<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Ocarina — précis</title>
    <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/</link>
    <description>Recent content on Ocarina — précis</description>
    <generator>Hugo</generator>
    <language>fr-FR</language>
    <lastBuildDate>Thu, 04 Jun 2026 21:25:43 +0200</lastBuildDate>
    <atom:link href="https://mojo-molotov.github.io/from-ocarina-to-igor/fr/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>00.01 — Cartographie de l&#39;écosystème</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/00-big-picture/01-ecosystem-map/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/00-big-picture/01-ecosystem-map/</guid>
      <description>&lt;h1 id=&#34;0001cartographie-de-lécosystème&#34;&gt;00.01 — Cartographie de l&amp;rsquo;écosystème&lt;a class=&#34;anchor&#34; href=&#34;#0001cartographie-de-l%c3%a9cosyst%c3%a8me&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;p&gt;L&amp;rsquo;écosystème Ocarina est constitué de &lt;strong&gt;six dépôts publics&lt;/strong&gt; sur le compte&#xA;GitHub &lt;a href=&#34;https://github.com/mojo-molotov&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;mojo-molotov&lt;/code&gt;&lt;/a&gt;. Ils s&amp;rsquo;organisent en&#xA;&lt;strong&gt;cinq rôles distincts&lt;/strong&gt; qui se composent :&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Rôle&lt;/th&gt;&#xA;          &lt;th&gt;Dépôt&lt;/th&gt;&#xA;          &lt;th&gt;Forme&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Framework&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ocarina&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Bibliothèque Python publiée sur PyPI&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Application sous test (SUT) publique&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;igoristan&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;SPA React + Vike déployée sur GitHub Pages&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Backend de coordination&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;tests-workers&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;API Vercel Edge + Upstash Redis&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Suite d&amp;rsquo;exemples&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ocarina-example&lt;/code&gt; &lt;em&gt;(canonique)&lt;/em&gt;, &lt;code&gt;ocarina-with-ai-example&lt;/code&gt; &lt;em&gt;(IA)&lt;/em&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Projets Python e2e tournés vers l&amp;rsquo;Igoristan / CURA&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Documentation publique&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ocarina-holy-book&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Site VitePress FR + EN, PDF, skills IA&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;schéma-densemble-big-picture&#34;&gt;Schéma d&amp;rsquo;ensemble (big picture)&lt;a class=&#34;anchor&#34; href=&#34;#sch%c3%a9ma-densemble-big-picture&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────────────────────────┐&#xA;│                              OCARINA (BIG PICTURE)                              │&#xA;└─────────────────────────────────────────────────────────────────────────────────┘&#xA;&#xA;   ┌──────────────────────────────┐         ┌──────────────────────────────────┐&#xA;   │  [PACKAGE]  ocarina          │ ──────► │  [PACKAGE]  ocarina-example      │&#xA;   │  Framework Python 3.14+      │   pip   │  Suite e2e contre l&amp;#39;Igoristan    │&#xA;   │  ROP / DSL / orchestration   │ install │  (Selenium + Firefox + Redis)    │&#xA;   └──────────────────────────────┘         └──────────────────────────────────┘&#xA;            │  │                                       │  ▲   │&#xA;            │  │                                       │  │  utilise&#xA;            │  │ pip install ocarina                   │  │  IGOR_API_KEY&#xA;            │  ▼                                       ▼  │   │&#xA;            │  ┌────────────────────────────────────┐  pilote │&#xA;            │  │ [PACKAGE]  ocarina-with-ai-example │  ┌──────┴──────────────────┐&#xA;            │  │ Suite e2e CURA Healthcare          │  │ [WEBSITE]  igoristan    │&#xA;            │  │ Claude Code, 99% machine           │  │ React 19 + Vike         │&#xA;            │  └────────────────────────────────────┘  │ GitHub Pages            │&#xA;            │              │                           └──────┬──────────────────┘&#xA;            │              │ pilote                           │ fetch OTP&#xA;            │              ▼                                  ▼&#xA;            │   ┌──────────────────────────────┐    ┌──────────────────────────────┐&#xA;            │   │ [WEBSITE]  CURA Healthcare   │    │ [BACKEND]  tests-workers     │&#xA;            │   │ Heroku, PHP open source      │    │ Vercel Edge Functions        │&#xA;            │   └──────────────────────────────┘    │ + Upstash Redis              │&#xA;            │                                       │ /api/otp, /otp-history,      │&#xA;            │                                       │ /api/corsicadex              │&#xA;            │                                       └──────────────────────────────┘&#xA;            │                                                 ▲&#xA;            │                                                 │ x-api-key&#xA;            │                                                 │ (IGOR_API_KEY ≡ API_SECRET)&#xA;            │                                                 │&#xA;            ▼&#xA;   ┌──────────────────────────────┐&#xA;   │  [DOCS]  ocarina-holy-book   │&#xA;   │  VitePress FR + EN + RU      │&#xA;   │  Skills IA, PDF, llms.txt    │&#xA;   │  GitHub Pages                │&#xA;   └──────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;ocarina&lt;/code&gt; → suites d&amp;rsquo;exemples&lt;/strong&gt; : dépendance Python classique (&lt;code&gt;pip install ocarina&lt;/code&gt;).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Suites d&amp;rsquo;exemples → SUT (Igoristan / CURA)&lt;/strong&gt; : pilotage navigateur via Selenium.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Suite Igoristan ↔ &lt;code&gt;tests-workers&lt;/code&gt;&lt;/strong&gt; : appels HTTP (OTP, Corsicadex) ; le secret partagé &lt;code&gt;IGOR_API_KEY&lt;/code&gt; (côté client) ≡ &lt;code&gt;API_SECRET&lt;/code&gt; (côté serveur).&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;responsabilités&#34;&gt;Responsabilités&lt;a class=&#34;anchor&#34; href=&#34;#responsabilit%c3%a9s&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Rôle&lt;/th&gt;&#xA;          &lt;th&gt;Contrat&lt;/th&gt;&#xA;          &lt;th&gt;Besoin&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Framework&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Donner&lt;/em&gt; le DSL, l&amp;rsquo;orchestration, la pool de drivers, les reporters&lt;/td&gt;&#xA;          &lt;td&gt;Doit être petit, auditable, sans dépendances cachées&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;SUT public&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Offrir&lt;/em&gt; un terrain de jeu volontairement chaotique (random errors, OTP, formulaires capricieux)&lt;/td&gt;&#xA;          &lt;td&gt;Hébergé en permanence sur GitHub Pages, sans backend lourd&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Backend de coordination&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Coordonner&lt;/em&gt; les workers sur l&amp;rsquo;OTP, fournir des données pour les tests parallèles&lt;/td&gt;&#xA;          &lt;td&gt;Stateless côté code, l&amp;rsquo;état vit dans Redis (Upstash)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Exemples&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Démontrer&lt;/em&gt; comment on utilise réellement Ocarina, à la fois sans IA (canonique) et avec IA (CURA)&lt;/td&gt;&#xA;          &lt;td&gt;Doivent être publics, exécutables, complets, suivis en CI&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Documentation&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Transmettre&lt;/em&gt; le pourquoi, le comment, et l&amp;rsquo;usage IA&lt;/td&gt;&#xA;          &lt;td&gt;Doit exister en EN + FR + RU (tradition corso-russe), exposer des fichiers adaptés aux LLMs comme &lt;code&gt;llms.txt&lt;/code&gt;, générer des PDF&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;trois-licences&#34;&gt;Trois licences&lt;a class=&#34;anchor&#34; href=&#34;#trois-licences&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Licence&lt;/th&gt;&#xA;          &lt;th&gt;Dépôts concernés&lt;/th&gt;&#xA;          &lt;th&gt;Pourquoi&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;MIT&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ocarina&lt;/code&gt;, &lt;code&gt;ocarina-example&lt;/code&gt;, &lt;code&gt;ocarina-with-ai-example&lt;/code&gt;, &lt;code&gt;ocarina-holy-book&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Tout ce qui est code Python ou doc, transmissible « &lt;em&gt;tel quel&lt;/em&gt; », souverain — voir &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/11-independence/&#34;&gt;&lt;code&gt;../11-independence/&lt;/code&gt;&lt;/a&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;ISC&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;tests-workers&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Choix par défaut du &lt;em&gt;scaffold&lt;/em&gt; Vercel Edge, conservé.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;em&gt;(aucune licence)&lt;/em&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;igoristan&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Application démo publique, hébergée par l&amp;rsquo;auteur (à considérer comme WTFPL).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;points-de-friction-volontaires-entre-dépôts&#34;&gt;Points de friction volontaires entre dépôts&lt;a class=&#34;anchor&#34; href=&#34;#points-de-friction-volontaires-entre-d%c3%a9p%c3%b4ts&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Le design est &lt;strong&gt;volontairement asymétrique&lt;/strong&gt; sur certains axes ; ces frictions sont des outils de test :&lt;/p&gt;</description>
    </item>
    <item>
      <title>01.01 — Prendre le problème à l&#39;envers</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/01-philosophy/01-flip-the-problem/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/01-philosophy/01-flip-the-problem/</guid>
      <description>&lt;h1 id=&#34;0101prendre-le-problème-à-lenvers&#34;&gt;01.01 — Prendre le problème à l&amp;rsquo;envers&lt;a class=&#34;anchor&#34; href=&#34;#0101prendre-le-probl%c3%a8me-%c3%a0-lenvers&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;h2 id=&#34;le-constat&#34;&gt;Le constat&lt;a class=&#34;anchor&#34; href=&#34;#le-constat&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Tous les frameworks de test e2e contemporains ont été construits sur un postulat implicite : il existe une &lt;strong&gt;barrière&lt;/strong&gt; entre &lt;em&gt;ceux qui codent&lt;/em&gt; et &lt;em&gt;ceux qui définissent les tests&lt;/em&gt;. Cette barrière est tenue pour acquise : méthodologiquement, organisationnellement, parfois contractuellement.&lt;/p&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;La plupart des frameworks de test ont été conçus dans un monde où la barrière entre « &lt;em&gt;ceux qui codent&lt;/em&gt; » et « &lt;em&gt;ceux qui définissent les tests&lt;/em&gt; » était réelle et structurelle.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.01 — Identité technique d&#39;Ocarina</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/01-identity/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/01-identity/</guid>
      <description>&lt;h1 id=&#34;0201identité-technique-docarina&#34;&gt;02.01 — Identité technique d&amp;rsquo;Ocarina&lt;a class=&#34;anchor&#34; href=&#34;#0201identit%c3%a9-technique-docarina&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;h2 id=&#34;pyprojecttoml&#34;&gt;&lt;code&gt;pyproject.toml&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#pyprojecttoml&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[project]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;name = &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ocarina&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;version = &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;1.1.10&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;description = &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Websites test framework for Igor&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;requires-python = &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;gt;=3.14&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;authors = [{ name=&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Igor Casanova&amp;#34;&lt;/span&gt;, email=&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;[REDACTED]&amp;#34;&lt;/span&gt; }]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;license = &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;MIT&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;license-files = [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;LICEN[CS]E*&amp;#34;&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;readme = &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;README.md&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dependencies = [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;python-docx&amp;gt;=1.2.0&amp;#34;&lt;/span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;version = &amp;quot;1.1.10&amp;quot;&lt;/code&gt;&lt;/strong&gt; : le projet est en &lt;em&gt;stable 1.x&lt;/em&gt;, pas en pré-version.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;requires-python = &amp;quot;&amp;gt;=3.14&amp;quot;&lt;/code&gt;&lt;/strong&gt; : le typage générique PEP 695 est utilisé partout.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;dependencies = [&amp;quot;python-docx&amp;gt;=1.2.0&amp;quot;]&lt;/code&gt;&lt;/strong&gt; : &lt;strong&gt;une seule&lt;/strong&gt; dépendance d&amp;rsquo;exécution. Tout le reste est dans &lt;code&gt;dev&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;license = &amp;quot;MIT&amp;quot;&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;description = &amp;quot;Websites test framework for Igor&amp;quot;&lt;/code&gt;&lt;/strong&gt; : pas « &lt;em&gt;for everyone&lt;/em&gt; », pas « &lt;em&gt;for humans&lt;/em&gt; », explicitement « &lt;em&gt;for Igor&lt;/em&gt; ». Cohérent avec &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/01-philosophy/05-political-stance/&#34;&gt;&lt;code&gt;../01-philosophy/05-political-stance.md&lt;/code&gt;&lt;/a&gt; : « &lt;em&gt;C&amp;rsquo;est ma voiture.&lt;/em&gt; »&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;dépendances-dev&#34;&gt;Dépendances &lt;code&gt;dev&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#d%c3%a9pendances-dev&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[dependency-groups]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dev = [&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ruff&amp;gt;=0.15.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;mypy&amp;gt;=1.17.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;mypy-extensions&amp;gt;=1.1.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;typing-extensions&amp;gt;=4.14.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;pytest&amp;gt;=9.0.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;pytest-cov&amp;gt;=7.0.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;hypothesis&amp;gt;=6.151.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;pytest-mypy-plugins&amp;gt;=3.1.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;allure-pytest&amp;gt;=2.15.3&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;allure-python-commons&amp;gt;=2.15.3&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;pre-commit&amp;gt;=4.5.1&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;syrupy&amp;gt;=5.1.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;selenium&amp;gt;=4.40.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;playwright&amp;gt;=1.60.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;twine&amp;gt;=6.2.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;build&amp;gt;=1.4.2&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;prysk&amp;gt;=0.20.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Outil&lt;/th&gt;&#xA;          &lt;th&gt;Rôle dans Ocarina&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ruff&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Linter + formatter (remplace flake8 + isort + black). &lt;code&gt;select = [&amp;quot;ALL&amp;quot;]&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;mypy&lt;/code&gt; (+ extensions)&lt;/td&gt;&#xA;          &lt;td&gt;Type-checker. &lt;code&gt;strict = true&lt;/code&gt; (cf. &lt;code&gt;mypy.ini&lt;/code&gt;).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;pytest&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Runner des tests unitaires (le framework est lui-même testé).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;pytest-cov&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Couverture de tests (du framework lui-même). Configurée dans &lt;code&gt;pyproject.toml#tool.coverage&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;hypothesis&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Property-based testing (PBT)&lt;/em&gt; → &lt;code&gt;test_invariants_properties.py&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;pytest-mypy-plugins&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Tests &lt;strong&gt;statiques&lt;/strong&gt; de typage (vérifie les erreurs &lt;em&gt;mypy&lt;/em&gt; attendues et tests d&amp;rsquo;inférence de types avec &lt;code&gt;reveal_type&lt;/code&gt;).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;allure-pytest&lt;/code&gt; / &lt;code&gt;allure-python-commons&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Rapport Allure (déployé sur GH Pages).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;pre-commit&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Hooks Git locaux (ruff-format).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;syrupy&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Snapshot testing&lt;/em&gt; (sortie de &lt;code&gt;pretty_print_results&lt;/code&gt;, &lt;code&gt;results_to_json&lt;/code&gt;).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;selenium&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Présent en &lt;code&gt;dev&lt;/code&gt; parce qu&amp;rsquo;Ocarina &lt;strong&gt;ne dépend pas&lt;/strong&gt; de Selenium pour fonctionner, livre juste un &lt;em&gt;adapter&lt;/em&gt; Selenium pour être immédiatement opérationnel.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;playwright&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Même logique : un second &lt;em&gt;adapter&lt;/em&gt; livré (depuis la &lt;code&gt;1.1.3&lt;/code&gt;). Ocarina pilote désormais &lt;strong&gt;Selenium et Playwright&lt;/strong&gt; out of the box, derrière les mêmes ports.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;twine&lt;/code&gt; / &lt;code&gt;build&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Publication PyPI.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;prysk&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Tests CLI au format &lt;em&gt;cram&lt;/em&gt; (&lt;code&gt;.t&lt;/code&gt;). Successeur de &lt;code&gt;cram&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;mypyini&#34;&gt;&lt;code&gt;mypy.ini&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#mypyini&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-ini&#34; data-lang=&#34;ini&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;[mypy]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;python_version&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;3.14&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;strict&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;true&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# * ... Allow missing annotations (type inference is cool)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;disallow_incomplete_defs&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# * ... Allow missing annotations (type inference is cool)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;disallow_untyped_defs&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;false&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;strict = true&lt;/code&gt;&lt;/strong&gt; : active &lt;code&gt;--warn-redundant-casts&lt;/code&gt;, &lt;code&gt;--warn-unused-ignores&lt;/code&gt;, &lt;code&gt;--no-implicit-optional&lt;/code&gt;, &lt;code&gt;--check-untyped-defs&lt;/code&gt;, etc.&lt;/li&gt;&#xA;&lt;li&gt;Deux exceptions : &lt;code&gt;disallow_incomplete_defs&lt;/code&gt; et &lt;code&gt;disallow_untyped_defs&lt;/code&gt; désactivés, car l&amp;rsquo;auteur estime que l&amp;rsquo;&lt;strong&gt;inférence&lt;/strong&gt; de type de &lt;em&gt;mypy&lt;/em&gt; est suffisante quand on n&amp;rsquo;a pas besoin d&amp;rsquo;expliciter. Toutes les annotations explicites sont là où elles &lt;strong&gt;comptent&lt;/strong&gt;.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;pyprojecttomltoolruff&#34;&gt;&lt;code&gt;pyproject.toml#tool.ruff&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#pyprojecttomltoolruff&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[tool.ruff.lint]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;select = [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ALL&amp;#34;&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ignore = [&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ANN002&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ANN003&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ANN201&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;TRY003&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;C901&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;D203&amp;#34;&lt;/span&gt;,    &lt;span style=&#34;color:#6272a4&#34;&gt;# conflit avec D211&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;D213&amp;#34;&lt;/span&gt;,    &lt;span style=&#34;color:#6272a4&#34;&gt;# conflit avec D212&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;COM812&amp;#34;&lt;/span&gt;,  &lt;span style=&#34;color:#6272a4&#34;&gt;# conflit avec le formatter ruff (auto-fix)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[tool.ruff]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;exclude = [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;**/.venv/**&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;**/bin/**&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;**/__init__.py&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;**/__bypass_linter__&amp;#34;&lt;/span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;select = [&amp;quot;ALL&amp;quot;]&lt;/code&gt;&lt;/strong&gt; : toutes les règles de &lt;em&gt;ruff&lt;/em&gt; sont activées (~800).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Ignore list courte&lt;/strong&gt; : six règles seulement, toutes justifiées.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;B008&lt;/code&gt; NE doit JAMAIS être ignoré&lt;/strong&gt; (« &lt;em&gt;&lt;code&gt;# &amp;quot;B008&amp;quot;,  # * ... NEVER ignore this rule without knowing very well what you are doing: https://docs.astral.sh/ruff/rules/function-call-in-default-argument/&lt;/code&gt;&lt;/em&gt; »). C&amp;rsquo;est une note pour ne pas répéter une erreur passée.&lt;/li&gt;&#xA;&lt;li&gt;**&lt;code&gt;**/**bypass_linter**&lt;/code&gt;** : convention de répertoire pour héberger du code explicitement non-linté (utile pour des modules expérimentaux internes).&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;pyprojecttomltoolpytestini_options&#34;&gt;&lt;code&gt;pyproject.toml#tool.pytest.ini_options&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#pyprojecttomltoolpytestini_options&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;testpaths = [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;tests&amp;#34;&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;python_files = [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;test_*.py&amp;#34;&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;norecursedirs = [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;.*&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;__pycache__&amp;#34;&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;log_cli = &lt;span style=&#34;color:#ff79c6&#34;&gt;true&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;log_cli_level = &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;DEBUG&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;log_cli_format = &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;%(asctime)s [%(levelname)s] %(name)s: %(message)s&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;log_cli_date_format = &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;%Y-%m-%d %H:%M:%S&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;addopts = &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;--cov=src&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;--cov-branch&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;--cov-report=term-missing&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;--cov-report=html&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;--cov-report=xml:coverage.xml&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Réglage&lt;/th&gt;&#xA;          &lt;th&gt;Effet&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;log_cli = true&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Tous les logs des tests apparaissent en CLI (utile pour les scénarios qui passent par &lt;code&gt;ILogger&lt;/code&gt;).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;--cov=src&lt;/code&gt; + &lt;code&gt;--cov-branch&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Couverture par branche, pas seulement par ligne.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;--cov-report=html&lt;/code&gt; + &lt;code&gt;xml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Trois sorties : terminal, HTML local, XML pour ingestion CI.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;pyprojecttomltoolcoverage&#34;&gt;&lt;code&gt;pyproject.toml#tool.coverage&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#pyprojecttomltoolcoverage&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Voir le détail ici : &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/07-coverage-policy/&#34;&gt;&lt;code&gt;../04-internal-tests/07-coverage-policy.md&lt;/code&gt;&lt;/a&gt;. En bref : tout ce qui est &lt;em&gt;shape&lt;/em&gt; (custom_types, ports, errors), ce qui requiert un navigateur réel (adapters Selenium), et ce qui est traversé par les cram tests (cli/store, cli/builder), est explicitement omis. Pour ne pas &lt;strong&gt;fausser&lt;/strong&gt; les métriques.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.03.01 — Le type Result[T]</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/01-result/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/01-result/</guid>
      <description>&lt;h1 id=&#34;020301le-type-resultt&#34;&gt;02.03.01 — Le type &lt;code&gt;Result[T]&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#020301le-type-resultt&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/railway/result.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/railway/result.py&lt;/code&gt;&lt;/a&gt; — &lt;strong&gt;zéro dépendance&lt;/strong&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;code&#34;&gt;Code&lt;a class=&#34;anchor&#34; href=&#34;#code&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; dataclasses &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; dataclass, field&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; typing &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; TypeGuard, final&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_BaseResult&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&amp;#34;Base class ensuring both Ok and Fail have error attribute for type narrowing.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    error: Exception &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@final&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@dataclass(frozen&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;True&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;Ok&lt;/span&gt;[T](_BaseResult):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    value: T&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    error: &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@final&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@dataclass(frozen&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;True&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;Fail&lt;/span&gt;(_BaseResult):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    error: Exception &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; field(default_factory&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;lambda&lt;/span&gt;: Exception(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Unknown error&amp;#34;&lt;/span&gt;))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; Result[T] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Ok[T] &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; Fail&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;is_ok&lt;/span&gt;[T](result: Result[T]) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; TypeGuard[Ok[T]]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;isinstance&lt;/span&gt;(result, Ok)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;is_fail&lt;/span&gt;[T](result: Result[T]) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; TypeGuard[Fail]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;isinstance&lt;/span&gt;(result, Fail)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;six-décisions-de-design-à-décortiquer&#34;&gt;Six décisions de design à décortiquer&lt;a class=&#34;anchor&#34; href=&#34;#six-d%c3%a9cisions-de-design-%c3%a0-d%c3%a9cortiquer&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;1-_baseresult-comme-parent-commun&#34;&gt;1. &lt;code&gt;_BaseResult&lt;/code&gt; comme parent commun&lt;a class=&#34;anchor&#34; href=&#34;#1-_baseresult-comme-parent-commun&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_BaseResult&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    error: Exception &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But unique : permettre à &lt;em&gt;mypy&lt;/em&gt; d&amp;rsquo;&lt;strong&gt;accéder à &lt;code&gt;result.error&lt;/code&gt; sans narrowing préalable&lt;/strong&gt;. Sans cette base, on devrait écrire :&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.04.01 — Le flot validate → assert_that → execute → raise_if_invalid</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/04-invariants/01-validate-flow/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/04-invariants/01-validate-flow/</guid>
      <description>&lt;h1 id=&#34;020401le-flot-validate--assert_that--execute--raise_if_invalid&#34;&gt;02.04.01 — Le flot &lt;code&gt;validate → assert_that → execute → raise_if_invalid&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#020401le-flot-validate--assert_that--execute--raise_if_invalid&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/invariants/validate.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/invariants/validate.py&lt;/code&gt;&lt;/a&gt; (entry-point) + &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/invariants/internals/validation_chain.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/invariants/internals/validation_chain.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;la-machine-à-états&#34;&gt;La machine à états&lt;a class=&#34;anchor&#34; href=&#34;#la-machine-%c3%a0-%c3%a9tats&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;diagramme&#34;&gt;Diagramme&lt;a class=&#34;anchor&#34; href=&#34;#diagramme&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;validate(value: T)&#xA;       │&#xA;       ▼&#xA;ValidationStartBlock[T]&#xA;       │&#xA;       └─ assert_that(predicate, *, msg=None)&#xA;              │&#xA;              ▼&#xA;       ValidationAssertBlock[T]   ← état qui accepte plusieurs continuations&#xA;              │&#xA;              ├─► assert_that(P)     [boucle, AND chaînable, voir le diagramme dédié]&#xA;              ├─► otherwise(P)       [OR, voir 03-otherwise-any-of.md]&#xA;              ├─► then(U, name=...)  [change de valeur, voir 04-then-chain-of-validations.md]&#xA;              └─► execute()          → _ValidationResult&#xA;                                          │&#xA;                                          ├─ is_valid: bool&#xA;                                          ├─ errors: Sequence[InvariantViolationError]&#xA;                                          ├─ validated_values: Sequence[Any]&#xA;                                          └─ raise_if_invalid() : None | raise AggregateInvariantViolationError&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;boucle-assert_that&#34;&gt;Boucle &lt;code&gt;assert_that&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#boucle-assert_that&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;&lt;code&gt;assert_that&lt;/code&gt; formule &lt;strong&gt;une&lt;/strong&gt; assertion sur la valeur courante. Plusieurs &lt;code&gt;assert_that&lt;/code&gt; peuvent se suivre, chacun ajoutant une condition supplémentaire à la chaîne (AND logique, ordre préservé).&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.05.01 — Test[Driver]</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/05-orchestration/01-test/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/05-orchestration/01-test/</guid>
      <description>&lt;h1 id=&#34;020501testdriver&#34;&gt;02.05.01 — &lt;code&gt;Test[Driver]&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#020501testdriver&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing/oc_test.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/testing/oc_test.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;signature&#34;&gt;Signature&lt;a class=&#34;anchor&#34; href=&#34;#signature&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@final&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;Test&lt;/span&gt;[Driver]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;__init__&lt;/span&gt;(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name: TestName,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        test_id: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        test_scenario: TestScenario[Driver],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        pre_test_scenarios_fragments: Sequence[TestScenarioFragment[Driver]] &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        post_test_scenarios_fragments: Sequence[TestScenarioFragment[Driver]] &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        skipped: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;False&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; test_id &lt;span style=&#34;color:#ff79c6&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            test_id &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; name&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; name&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;test_id &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; test_id&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_test_scenario &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; test_scenario&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_pre_test_scenarios_fragments &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; pre_test_scenarios_fragments &lt;span style=&#34;color:#ff79c6&#34;&gt;or&lt;/span&gt; []&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_post_test_scenarios_fragments &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; post_test_scenarios_fragments &lt;span style=&#34;color:#ff79c6&#34;&gt;or&lt;/span&gt; []&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_skipped &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; skipped&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Six paramètres :&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Paramètre&lt;/th&gt;&#xA;          &lt;th&gt;Type&lt;/th&gt;&#xA;          &lt;th&gt;Rôle&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestName&lt;/code&gt; (&lt;code&gt;str&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;Label humain ; apparaît dans le rapport, et &lt;strong&gt;devient le nom du fichier de log&lt;/strong&gt; (donc soumis à &lt;code&gt;is_valid_filename&lt;/code&gt;).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;test_id&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;str | None&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Identifiant stable pour &lt;code&gt;--only&lt;/code&gt; / &lt;code&gt;--exclude&lt;/code&gt;. Si absent : prend &lt;code&gt;name&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;test_scenario&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestScenario[Driver]&lt;/code&gt; (alias = &lt;code&gt;Callable[[Driver, ILogger], Scenario[Driver]]&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Factory&lt;/em&gt; qui construit le &lt;code&gt;Scenario&lt;/code&gt; au moment de l&amp;rsquo;exécution.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;pre_test_scenarios_fragments&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Sequence[TestScenarioFragment[Driver]] | None&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Fonctions &lt;code&gt;(driver, logger) -&amp;gt; TestChain&lt;/code&gt; exécutées &lt;strong&gt;avant&lt;/strong&gt; le scénario principal.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;post_test_scenarios_fragments&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Sequence[TestScenarioFragment[Driver]] | None&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Fonctions &lt;code&gt;(driver, logger) -&amp;gt; TestChain&lt;/code&gt; exécutées &lt;strong&gt;après&lt;/strong&gt; le scénario principal.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;skipped&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Si &lt;code&gt;True&lt;/code&gt;, le test est enregistré mais pas exécuté.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h3 id=&#34;1-final&#34;&gt;1. &lt;code&gt;@final&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#1-final&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;Pas d&amp;rsquo;héritage utilisateur. Si l&amp;rsquo;on veut un test « &lt;em&gt;spécial&lt;/em&gt; », on &lt;em&gt;compose&lt;/em&gt; via les fragments ou via un scénario, on ne sous-classe pas.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.10.01 — WebDriversPool[Driver]</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/01-drivers-pool/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/01-drivers-pool/</guid>
      <description>&lt;h1 id=&#34;021001webdriverspooldriver&#34;&gt;02.10.01 — &lt;code&gt;WebDriversPool[Driver]&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#021001webdriverspooldriver&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/infra/drivers_pool.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/infra/drivers_pool.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Pool thread-safe de drivers, max concurrence garantie par sémaphore, &lt;em&gt;warmup&lt;/em&gt; asynchrone surveillé, &lt;em&gt;shutdown&lt;/em&gt; propre. Pas de réutilisation : chaque &lt;code&gt;acquire()&lt;/code&gt; détruit son driver à la fin (état propre).&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;constructeur&#34;&gt;Constructeur&lt;a class=&#34;anchor&#34; href=&#34;#constructeur&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@final&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;WebDriversPool&lt;/span&gt;[Driver]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;__init__&lt;/span&gt;(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        create_driver: Thunk[BuiltWebDriver[Driver]],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        max_size: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        warmup_timeout: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;float&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_create_driver &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; create_driver&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_pool: Queue[BuiltWebDriver[Driver]] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Queue(max_size)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_semaphore &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Semaphore(max_size)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_warmup_timeout &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; (&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            warmup_timeout &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; warmup_timeout &lt;span style=&#34;color:#ff79c6&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;and&lt;/span&gt; warmup_timeout &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;0.1&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;60.0&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;5&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Primitive&lt;/th&gt;&#xA;          &lt;th&gt;Rôle&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;Queue(max_size)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;File des drivers &lt;em&gt;pré-créés&lt;/em&gt; disponibles.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;Semaphore(max_size)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Garantit que &lt;strong&gt;jamais plus de &lt;code&gt;max_size&lt;/code&gt; drivers&lt;/strong&gt; ne vivent simultanément (qu&amp;rsquo;ils soient en queue ou acquis).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;warmup_timeout&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Délai max sans progression de warmup avant de lever &lt;code&gt;WarmupTimeoutError&lt;/code&gt;. Par défaut 300s.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;acquire&#34;&gt;&lt;code&gt;acquire()&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#acquire&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@contextmanager&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;acquire&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; Iterator[Driver]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;try&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        driver, dispose &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_pool&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;get_nowait()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;except&lt;/span&gt; Empty:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_semaphore&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;acquire()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;try&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            driver, dispose &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_create_driver()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;except&lt;/span&gt; Exception:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_semaphore&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;release()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;raise&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;try&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;yield&lt;/span&gt; driver&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;finally&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt; suppress(Exception):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            dispose()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_semaphore&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;release()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;              acquire()&#xA;                  │&#xA;                  ▼&#xA;   ┌──────────────────────────────────┐&#xA;   │ pool.get_nowait()                │── OK ─► driver, dispose         ← cas warmup&#xA;   └──────────────┬───────────────────┘&#xA;                  │ Empty&#xA;                  ▼&#xA;   ┌──────────────────────────────────┐&#xA;   │ sem.acquire()                    │  ← bloque si N drivers vivants&#xA;   └──────────────┬───────────────────┘&#xA;                  ▼&#xA;   ┌──────────────────────────────────┐&#xA;   │ create_driver()                  │── leve ─► sem.release(); raise&#xA;   └──────────────┬───────────────────┘&#xA;                  ▼&#xA;   ┌──────────────────────────────────┐&#xA;   │ yield driver                     │  ← caller utilise&#xA;   └──────────────┬───────────────────┘&#xA;                  ▼&#xA;                finally :&#xA;                  with suppress : dispose()       ← driver détruit&#xA;                  sem.release()                   ← rend une place&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;1-pas-de-réutilisation&#34;&gt;1. Pas de réutilisation&lt;a class=&#34;anchor&#34; href=&#34;#1-pas-de-r%c3%a9utilisation&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;&lt;code&gt;Queue.get_nowait()&lt;/code&gt; &lt;em&gt;consomme&lt;/em&gt; l&amp;rsquo;entrée. Une fois sorti, le driver n&amp;rsquo;est jamais remis dans la queue. À la fin (&lt;code&gt;finally&lt;/code&gt;), il est &lt;strong&gt;disposé&lt;/strong&gt;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.11.01 — CliBuilder &#43; CliArg &#43; _SilentArgumentParser</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/11-opinionated/01-cli-builder/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/11-opinionated/01-cli-builder/</guid>
      <description>&lt;h1 id=&#34;021101clibuilder--cliarg--_silentargumentparser&#34;&gt;02.11.01 — &lt;code&gt;CliBuilder&lt;/code&gt; + &lt;code&gt;CliArg&lt;/code&gt; + &lt;code&gt;_SilentArgumentParser&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#021101clibuilder--cliarg--_silentargumentparser&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/opinionated/cli/builder.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/opinionated/cli/builder.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Surcouche déclarative au-dessus d&amp;rsquo;&lt;code&gt;argparse&lt;/code&gt;. Permet d&amp;rsquo;&lt;em&gt;agréger&lt;/em&gt; les erreurs de validation, &lt;em&gt;ré-écrire&lt;/em&gt; la sortie d&amp;rsquo;aide en cas d&amp;rsquo;erreur, et &lt;em&gt;enregistrer&lt;/em&gt; des effets post-parse.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;_silentargumentparser&#34;&gt;&lt;code&gt;_SilentArgumentParser&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#_silentargumentparser&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_SilentArgumentParser&lt;/span&gt;(ArgumentParser):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;error&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, message: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; Never:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&amp;#34;Raise an error.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;raise&lt;/span&gt; ValueError(message)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;L&amp;rsquo;&lt;code&gt;ArgumentParser&lt;/code&gt; standard appelle &lt;code&gt;sys.exit(2)&lt;/code&gt; directement en cas d&amp;rsquo;erreur. Avec cette merde, on &lt;strong&gt;ne peut pas&lt;/strong&gt; intercepter, on &lt;strong&gt;ne peut pas&lt;/strong&gt; agréger plusieurs erreurs.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;_SilentArgumentParser&lt;/code&gt; &lt;em&gt;re-route&lt;/em&gt; les erreurs en &lt;code&gt;ValueError&lt;/code&gt;. Donc &lt;code&gt;CliBuilder.parse&lt;/code&gt; peut les attraper et les agréger.&lt;/p&gt;</description>
    </item>
    <item>
      <title>03.01 — Effect, Thunk[T], Result[T]</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/03-functional/01-effect-thunk-result/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/03-functional/01-effect-thunk-result/</guid>
      <description>&lt;h1 id=&#34;0301effect-thunkt-resultt&#34;&gt;03.01 — &lt;code&gt;Effect&lt;/code&gt;, &lt;code&gt;Thunk[T]&lt;/code&gt;, &lt;code&gt;Result[T]&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0301effect-thunkt-resultt&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Trois lignes de code dans &lt;code&gt;custom_types/&lt;/code&gt; + une dans &lt;code&gt;railway/&lt;/code&gt;. Tout le DSL repose dessus.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;triplet&#34;&gt;Triplet&lt;a class=&#34;anchor&#34; href=&#34;#triplet&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/ocarina/custom_types/effect.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; Effect &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Callable[[], &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; Effects &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;tuple&lt;/span&gt;[Effect, &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/ocarina/custom_types/thunk.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; Thunk[T] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Callable[[], T]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/ocarina/railway/result.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; Result[T] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Ok[T] &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; Fail&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Type&lt;/th&gt;&#xA;          &lt;th&gt;Sémantique FP&lt;/th&gt;&#xA;          &lt;th&gt;Définition&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;Effect&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Effet de bord (déféré)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;() -&amp;gt; None&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;Thunk[T]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Computation&lt;/em&gt; déférée avec valeur&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;() -&amp;gt; T&lt;/code&gt; (paresse + valeur)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;Result[T]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Computation&lt;/em&gt; qui peut échouer&lt;/td&gt;&#xA;          &lt;td&gt;Union discriminée &lt;code&gt;Ok[T] | Fail&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;distinction-effect-vs-thunk&#34;&gt;Distinction Effect vs Thunk&lt;a class=&#34;anchor&#34; href=&#34;#distinction-effect-vs-thunk&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;log_msg: Effect &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;lambda&lt;/span&gt;: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;hello&amp;#34;&lt;/span&gt;)        &lt;span style=&#34;color:#6272a4&#34;&gt;# () -&amp;gt; None&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;get_42: Thunk[&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;lambda&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;42&lt;/span&gt;                 &lt;span style=&#34;color:#6272a4&#34;&gt;# () -&amp;gt; int&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;fetch:  Thunk[Result[&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;]] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;lambda&lt;/span&gt;: Ok(&lt;span style=&#34;color:#bd93f9&#34;&gt;42&lt;/span&gt;)     &lt;span style=&#34;color:#6272a4&#34;&gt;# () -&amp;gt; Result[int]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Si la fonction &lt;em&gt;retourne&lt;/em&gt; quelque chose qu&amp;rsquo;on va consommer ailleurs → &lt;code&gt;Thunk[T]&lt;/code&gt;.&lt;br&gt;&#xA;Sinon → &lt;code&gt;Effect&lt;/code&gt;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>04.01 — Stratégie « dehors comme un utilisateur »</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/01-strategy/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/01-strategy/</guid>
      <description>&lt;h1 id=&#34;0401stratégie-dehors-comme-un-utilisateur&#34;&gt;04.01 — Stratégie « &lt;em&gt;dehors comme un utilisateur&lt;/em&gt; »&lt;a class=&#34;anchor&#34; href=&#34;#0401strat%c3%a9gie-dehors-comme-un-utilisateur&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;C&amp;rsquo;est la posture documentée dès le &lt;code&gt;conftest.py&lt;/code&gt; des tests scénarios. Aucun test ne triche en regardant les internes.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;testsscenariosconftestpy&#34;&gt;&lt;code&gt;tests/scenarios/conftest.py&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#testsscenariosconftestpy&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Composant&lt;/th&gt;&#xA;          &lt;th&gt;Rôle&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;FakeDriver&lt;/code&gt; (dataclass)&lt;/td&gt;&#xA;          &lt;td&gt;Driver minimaliste : &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;disposed&lt;/code&gt;. Aucune méthode Selenium.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;make_built_driver()&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Factory &lt;code&gt;(FakeDriver, dispose)&lt;/code&gt;, soit la signature attendue par &lt;code&gt;WebDriversPool&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;make_pool(max_size=2)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;WebDriversPool[FakeDriver]&lt;/code&gt; prêt à l&amp;rsquo;emploi.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;RecordingPOM(POMBase)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;POM qui consigne ses appels et peut être configuré pour lever.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;acting(pom, step)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ActionSuccess[RecordingPOM]&lt;/code&gt; complet (failure+success en no-op).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;scenario_of(&amp;quot;ok&amp;quot;, &amp;quot;ok2&amp;quot;)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestScenario[FakeDriver]&lt;/code&gt; à N steps.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;failing_scenario(step=&amp;quot;boom&amp;quot;, exc=...)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Scénario à 1 step qui lève.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;make_test(name, scenario=..., test_id=..., skipped=False)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Test[FakeDriver]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;make_suite(name, tests, pool=..., transient_errors=..., max_retries_per_test=...)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestSuite[FakeDriver]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;make_campaign(name, suites, max_workers=1)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestCampaign[FakeDriver]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;make_cycle(name=&amp;quot;cycle&amp;quot;, campaigns=..., smoke=..., mode=...)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestCycle[FakeDriver]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;run_chain(runner)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Helper qui exécute un &lt;code&gt;ChainRunner&lt;/code&gt; et retourne le &lt;code&gt;ActionChain&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;fakedriver&#34;&gt;&lt;code&gt;FakeDriver&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#fakedriver&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@dataclass&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;FakeDriver&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    title: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;fake&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    disposed: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;False&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;C&amp;rsquo;est &lt;strong&gt;tout&lt;/strong&gt;.&#xA;&lt;strong&gt;Le cœur d&amp;rsquo;Ocarina n&amp;rsquo;appelle aucune API Selenium&lt;/strong&gt;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>05.01 — Stack de l&#39;Igoristan</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/05-igoristan/01-stack/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/05-igoristan/01-stack/</guid>
      <description>&lt;h1 id=&#34;0501stack-de-ligoristan&#34;&gt;05.01 — Stack de l&amp;rsquo;Igoristan&lt;a class=&#34;anchor&#34; href=&#34;#0501stack-de-ligoristan&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;h2 id=&#34;packagejson&#34;&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#packagejson&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;react&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^19.2.3&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;react-dom&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^19.2.3&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;vike&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^0.4.258&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;vike-react&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^0.6.21&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;valibot&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^1.2.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;react-hook-form&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^7.71.1&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;@hookform/resolvers&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^5.2.2&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;react-dropzone&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^14.3.8&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;lucide-react&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^0.562.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;usehooks-ts&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^3.1.1&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;throttleit&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^2.1.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;uuid&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^13.0.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;clsx&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^2.1.1&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;class-variance-authority&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^0.7.1&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;tailwind-merge&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^3.0.2&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;tailwindcss-animate&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^1.0.7&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;@radix-ui/react-slot&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^1.2.4&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Lib&lt;/th&gt;&#xA;          &lt;th&gt;Rôle&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;React 19.2&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;UI, hooks&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Vike 0.4&lt;/strong&gt; + &lt;code&gt;vike-react&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;SSG / SSR framework au-dessus de Vite (anciennement « &lt;em&gt;vite-plugin-ssr&lt;/em&gt; »)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Valibot 1.2&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Validation de schémas TS (alternative légère à Zod)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;react-hook-form&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Gestion de formulaires&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;react-dropzone&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Composant drag-and-drop pour upload&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;lucide-react&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Icones&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;usehooks-ts&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Hooks utilitaires (&lt;code&gt;useLocalStorage&lt;/code&gt;, etc.), utilisé par &lt;code&gt;useAuth&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;throttleit&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Throttle&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;uuid&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Génération d&amp;rsquo;IDs&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;clsx + tailwind-merge + class-variance-authority&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Utilitaires Tailwind&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;devdependencies&#34;&gt;&lt;code&gt;devDependencies&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#devdependencies&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;vite&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^7.3.1&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;@vitejs/plugin-react&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^5.1.2&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;@tailwindcss/vite&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^4.1.3&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;@tailwindcss/postcss&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^4.0.17&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;tailwindcss&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^4.1.1&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;terser&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^5.39.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;vite-plugin-compression&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^0.5.1&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;rollup-plugin-visualizer&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^5.14.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;@qalisa/vike-plugin-sitemap&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^1.7.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;typescript&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^6.0.3&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;typescript-eslint&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^8.59.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;eslint&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^9.18.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;@eslint/js&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^9.39.2&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;eslint-plugin-import-x&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^4.6.1&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;eslint-plugin-perfectionist&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^4.6.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;eslint-plugin-react&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^7.37.4&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;eslint-plugin-react-hooks&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^5.2.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;eslint-plugin-unused-imports&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^4.2.1&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;globals&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^15.15.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;prettier&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^3.5.3&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;prettier-plugin-tailwindcss&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^0.6.11&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;husky&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^9.1.7&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;lint-staged&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^15.5.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;commitizen&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^4.3.1&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;@commitlint/config-conventional&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^19.8.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;@commitlint/cz-commitlint&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^19.8.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;is-ci&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^4.1.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;editorconfig&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^2.0.1&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;minimatch&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^10.1.1&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;rimraf&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^6.0.1&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;wireit&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^0.14.11&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;engines&#34;&gt;&lt;code&gt;engines&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#engines&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;node&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^24.x&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;pnpm&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;11.x&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;→ Node 24, pnpm 11. &lt;strong&gt;&lt;code&gt;packageManager: pnpm@11.x&lt;/code&gt;&lt;/strong&gt; verrouille la version.&lt;/p&gt;</description>
    </item>
    <item>
      <title>06.01 — Stack Vercel Edge</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/06-tests-workers/01-stack-edge/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/06-tests-workers/01-stack-edge/</guid>
      <description>&lt;h1 id=&#34;0601stack-vercel-edge&#34;&gt;06.01 — Stack Vercel Edge&lt;a class=&#34;anchor&#34; href=&#34;#0601stack-vercel-edge&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;h2 id=&#34;packagejson&#34;&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#packagejson&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;name&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;tests-workers&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;version&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;1.0.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;scripts&amp;#34;&lt;/span&gt;: {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;vercel-build&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;echo &amp;#39;Build skipped&amp;#39;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;packageManager&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;pnpm@11.2.2&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;dependencies&amp;#34;&lt;/span&gt;: {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;@upstash/redis&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^1.36.2&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;otplib&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^13.2.1&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;devDependencies&amp;#34;&lt;/span&gt;: {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;@types/node&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^25.2.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;next&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^16.1.6&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;@vercel/node&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^5.5.28&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;typescript&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^5.9.3&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;license&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ISC&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Lib&lt;/th&gt;&#xA;          &lt;th&gt;Rôle&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;@upstash/redis&lt;/code&gt;&lt;/strong&gt; (runtime)&lt;/td&gt;&#xA;          &lt;td&gt;Client Redis &lt;em&gt;REST&lt;/em&gt; (pas TCP). Compatible Edge.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;otplib&lt;/code&gt;&lt;/strong&gt; (runtime)&lt;/td&gt;&#xA;          &lt;td&gt;Génération TOTP RFC 6238.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;next&lt;/code&gt;&lt;/strong&gt; (dev seulement)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Types&lt;/em&gt; uniquement (&lt;code&gt;NextRequest&lt;/code&gt;). Pas d&amp;rsquo;app Next.js.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;@vercel/node&lt;/code&gt;&lt;/strong&gt; (dev seulement)&lt;/td&gt;&#xA;          &lt;td&gt;Types Vercel.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;@types/node&lt;/code&gt;&lt;/strong&gt; (dev seulement)&lt;/td&gt;&#xA;          &lt;td&gt;Types Node 22+.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;Note : &lt;strong&gt;aucune&lt;/strong&gt; dépendance runtime au-delà de &lt;code&gt;@upstash/redis&lt;/code&gt; + &lt;code&gt;otplib&lt;/code&gt;.&lt;br&gt;&#xA;Le bundle est microscopique → cold start &amp;lt; 100ms.&lt;/p&gt;</description>
    </item>
    <item>
      <title>07.01 — Arborescence</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/01-tree/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/01-tree/</guid>
      <description>&lt;h1 id=&#34;0701arborescence&#34;&gt;07.01 — Arborescence&lt;a class=&#34;anchor&#34; href=&#34;#0701arborescence&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;src/&#xA;├── main.py                                                     # entry point (cf. README)&#xA;│&#xA;├── api/                                                        # clients HTTP externes&#xA;│   ├── retrieve_dashboard_otp_code.py                          # filter + sort + pick OTP&#xA;│   ├── get_otp_history.py                                      # HTTP GET /api/otp-history&#xA;│   └── constants/endpoints.py                                  # URLs des endpoints tests-workers&#xA;│&#xA;├── caches/                                                     # cache L1 in-memory&#xA;│   ├── l1.py                                                   # dogpile.cache.memory TTL 30m&#xA;│   └── reserve_free_cache_key.py                               # UUID + threading.Lock&#xA;│&#xA;├── constants/                                                  # constantes&#xA;│   ├── pages/&#xA;│   │   ├── homepage.py                                         # HOMEPAGE_URL&#xA;│   │   ├── dashboard.py                                        # DASHBOARD_URL + ids des inputs&#xA;│   │   ├── random_loaders.py&#xA;│   │   ├── sacred_upload.py&#xA;│   │   ├── corsicamon.py&#xA;│   │   ├── chaotic_form.py&#xA;│   │   ├── madness.py&#xA;│   │   ├── random_error_page.py&#xA;│   │   └── donkey_sausage_eater_detector.py&#xA;│   └── sys/&#xA;│       ├── redis_keys.py                                       # OTP_SEND_LOCK_KEY, ...&#xA;│       └── transient_errors.py                                 # (WebDriverException, HttpError...)&#xA;│&#xA;├── lib/&#xA;│   ├── connectors/test_steps/actions/                          # fonctions (TPOM) -&amp;gt; TPOM&#xA;│   │   ├── homepage.py&#xA;│   │   ├── dashboard_login.py                                  # ~10 connectors (with/without retries)&#xA;│   │   ├── dashboard_welcome.py&#xA;│   │   ├── dashboard_protected_page.py&#xA;│   │   ├── sacred_upload.py&#xA;│   │   ├── corsicamon_enter_api_key.py&#xA;│   │   ├── corsicamon_main.py&#xA;│   │   ├── chaotic_form.py&#xA;│   │   ├── random_error.py&#xA;│   │   ├── random_loaders.py&#xA;│   │   ├── madness.py&#xA;│   │   ├── this_is_bastia.py&#xA;│   │   ├── cors_errors.py&#xA;│   │   ├── bsod.py&#xA;│   │   ├── dsed.py&#xA;│   │   └── ids_bypassed.py&#xA;│   ├── custom_errors/&#xA;│   │   ├── http.py                                             # HttpErrorPageReachedError&#xA;│   │   └── transient_error.py                                  # TransientError&#xA;│   └── ext/                                                    # extensions / adapters&#xA;│       ├── ocarina/&#xA;│       │   ├── adapters/&#xA;│       │   │   ├── agnostic/&#xA;│       │   │   │   ├── act.py                                  # ⭐ adapter act (hook on_failure)&#xA;│       │   │   │   ├── env_getters.py                          # ⭐ adapter EnvGetters typé&#xA;│       │   │   │   └── match_page.py                           # ⭐ adapter match_page&#xA;│       │   │   └── selenium/&#xA;│       │   │       ├── test_suite.py                           # ⭐ adapter TestSuite&#xA;│       │   │       ├── test_campaign.py                        # ⭐ adapter TestCampaign&#xA;│       │   │       ├── cli_getters.py                          # getters typés depuis le CliStore&#xA;│       │   │       ├── logs.py                                 # create_just_log_error, etc.&#xA;│       │   │       └── screenshotter.py                        # take_screenshot helper&#xA;│       │   └── regex/error_page.py                             # ERROR_PAGE_REGEX&#xA;│       ├── redis/&#xA;│       │   └── client.py                                       # Singleton client Redis&#xA;│       └── selenium/&#xA;│           ├── humanize/                                       # ⭐ HumanizedDriver + keyboard&#xA;│           │   ├── proxy.py&#xA;│           │   └── keyboard.py&#xA;│           ├── pages/verify_elements_presence.py&#xA;│           └── watchers/catch_me_if_you_can_watcher.py         # ⭐ Watcher callback&#xA;│&#xA;├── pages/                                                      # POMs (Selenium + SeleniumTitleMixin)&#xA;│   ├── homepage.py&#xA;│   ├── random_loaders.py&#xA;│   ├── chaotic_form.py&#xA;│   ├── random_error.py&#xA;│   ├── sacred_upload/&#xA;│   │   ├── sacred_upload.py&#xA;│   │   └── fixtures/                                           # fichiers à uploader&#xA;│   ├── dashboard/&#xA;│   │   ├── login.py                                            # ⭐ avec OTP, retries, redis locks&#xA;│   │   ├── welcome_page.py&#xA;│   │   └── protected_page.py&#xA;│   ├── corsicamon/&#xA;│   │   ├── enter_api_key.py&#xA;│   │   └── main.py&#xA;│   ├── madness/&#xA;│   │   ├── base.py&#xA;│   │   ├── matchers.py                                         # has_cors, has_this_is_bastia&#xA;│   │   ├── cors.py&#xA;│   │   └── this_is_bastia.py&#xA;│   └── donkey_sausage_detector/&#xA;│       └── ids_bypassed.py&#xA;│&#xA;└── tests/&#xA;    ├── cycles/e2e.py                                           # ⭐ TestCycle (smoke + main)&#xA;    ├── campaigns/&#xA;    │   ├── global_smoke_tests.py                               # smoke&#xA;    │   ├── corsicamon.py                                       # main (+ smoke variant)&#xA;    │   ├── dashboard_login.py                                  # main&#xA;    │   ├── randomness.py                                       # main&#xA;    │   └── sacred_upload.py                                    # main&#xA;    ├── suites/&#xA;    │   ├── global_smoke_tests.py&#xA;    │   ├── randomness.py&#xA;    │   ├── dashboard/&#xA;    │   │   ├── access/&#xA;    │   │   │   ├── happy_paths.py&#xA;    │   │   │   └── unhappy_paths.py&#xA;    │   │   └── data_driven/multi_login.py&#xA;    │   ├── sacred_upload/{happy_paths,unhappy_paths}.py&#xA;    │   └── corsicamon/{smoke_tests,happy_paths,unhappy_paths}.py&#xA;    └── scenarios/&#xA;        ├── homepage/verify_homepage.py&#xA;        ├── dashboard/&#xA;        │   ├── access/{happy_paths,unhappy_paths}.py&#xA;        │   ├── back_to_igoristan.py&#xA;        │   └── data_driven/{multi_login,datasets/multi_login}.py&#xA;        ├── sacred_upload/{upload_files,just_go_back_to_igoristan}.py&#xA;        ├── corsicamon/{enter_api_key,new_draw,add_corsicamon,back_to_igoristan}.py&#xA;        └── randomness/&#xA;            ├── level_1/{random_error_page,random_loaders_page}.py&#xA;            ├── level_2/{dsed,madness}.py&#xA;            ├── level_3/chaotic_form.py&#xA;            └── level_4/walkthrough.py&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;sémantique-des-dossiers&#34;&gt;Sémantique des dossiers&lt;a class=&#34;anchor&#34; href=&#34;#s%c3%a9mantique-des-dossiers&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Dossier&lt;/th&gt;&#xA;          &lt;th&gt;Rôle&lt;/th&gt;&#xA;          &lt;th&gt;Convention&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;pages/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;POMs (héritent &lt;code&gt;SeleniumTitleMixin, POMBase&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;Une classe par page, fluent (&lt;code&gt;return self&lt;/code&gt;)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;lib/connectors/test_steps/actions/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Connectors &lt;code&gt;(TPOM) -&amp;gt; TPOM&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Fonctions pures ; si paramètres, closures&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;lib/custom_errors/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Exceptions projet&lt;/td&gt;&#xA;          &lt;td&gt;Sous-classes d&amp;rsquo;Exception&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;lib/ext/ocarina/adapters/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Adapters au-dessus d&amp;rsquo;Ocarina&lt;/td&gt;&#xA;          &lt;td&gt;Cf. &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/02-adapters/&#34;&gt;&lt;code&gt;02-adapters.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;lib/ext/redis/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Wrapper Singleton Redis&lt;/td&gt;&#xA;          &lt;td&gt;Pour les locks distribués&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;lib/ext/selenium/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Extensions Selenium spécifiques au projet&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;HumanizedDriver&lt;/code&gt;, &lt;code&gt;Watcher&lt;/code&gt; callback&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;caches/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Cache L1 in-memory&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;dogpile.cache.memory&lt;/code&gt; + UUID reservation&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;constants/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Constantes&lt;/td&gt;&#xA;          &lt;td&gt;URLs, redis keys, transient errors&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;api/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Clients HTTP externes&lt;/td&gt;&#xA;          &lt;td&gt;OTP retrieval, OTP history&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;tests/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Hiérarchie ISTQB&lt;/td&gt;&#xA;          &lt;td&gt;cycles / campaigns / suites / scenarios&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;lib-vs-pages&#34;&gt;&lt;code&gt;lib/&lt;/code&gt; vs &lt;code&gt;pages/&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#lib-vs-pages&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;pages/&lt;/code&gt;&lt;/strong&gt; : &lt;em&gt;POMs&lt;/em&gt;, encapsulent l&amp;rsquo;état d&amp;rsquo;une page (locators, méthodes d&amp;rsquo;action).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;lib/connectors/test_steps/actions/&lt;/code&gt;&lt;/strong&gt; : &lt;em&gt;connectors&lt;/em&gt;, fonctions qui &lt;em&gt;réfèrent&lt;/em&gt; aux POMs.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;act(on_homepage, open_homepage)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;#                ^^^^^^^^^^^^^&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;#                connector défini dans lib/connectors/test_steps/actions/homepage.py&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;open_homepage&lt;/span&gt;(p: Homepage) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; Homepage:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; p&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;open()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le &lt;code&gt;CLAUDE.md&lt;/code&gt; du projet IA en fait une discipline :&lt;/p&gt;</description>
    </item>
    <item>
      <title>08.01 — Code : 99% Claude, 1% Igor, intelligence : 50-50</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/08-ai-example/01-ai-manifesto/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/08-ai-example/01-ai-manifesto/</guid>
      <description>&lt;h1 id=&#34;0801code-99-claude-1-igor-intelligence-50-50&#34;&gt;08.01 — Code : 99% Claude, 1% Igor, intelligence : 50-50&lt;a class=&#34;anchor&#34; href=&#34;#0801code-99-claude-1-igor-intelligence-50-50&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;h2 id=&#34;readme&#34;&gt;README&lt;a class=&#34;anchor&#34; href=&#34;#readme&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;h2 id=&#34;a-note-to-the-reader&#34;&gt;A note to the reader&lt;a class=&#34;anchor&#34; href=&#34;#a-note-to-the-reader&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;This project is an experiment in AI-driven test engineering. In the interest of honesty about how it was built:&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;&lt;/th&gt;&#xA;          &lt;th&gt;Claude Code&lt;/th&gt;&#xA;          &lt;th&gt;Human&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Code written&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;99%&lt;/td&gt;&#xA;          &lt;td&gt;1%&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Intelligence&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;50%&lt;/td&gt;&#xA;          &lt;td&gt;50%&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;Almost every line was machine-written. The judgement behind it — what to test, what to distrust, when to dig and when to stop — was shared.&lt;/p&gt;</description>
    </item>
    <item>
      <title>09.01 — Stack VitePress &#43; Sugar Theme &#43; plugins maison</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/01-stack-vitepress/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/01-stack-vitepress/</guid>
      <description>&lt;h1 id=&#34;0901stack-vitepress--sugar-theme--plugins-maison&#34;&gt;09.01 — Stack VitePress + Sugar Theme + plugins maison&lt;a class=&#34;anchor&#34; href=&#34;#0901stack-vitepress--sugar-theme--plugins-maison&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;h2 id=&#34;packagejson&#34;&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#packagejson&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;dependencies&amp;#34;&lt;/span&gt;: {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;@sugarat/theme&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;0.5.17&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;devDependencies&amp;#34;&lt;/span&gt;: {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;@commitlint/config-conventional&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;20.5.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;@commitlint/cz-commitlint&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;20.5.1&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;@eslint/js&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;10.0.1&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;@types/node&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;25.6.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;commitizen&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;4.3.1&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;editorconfig&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;3.0.2&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;eslint&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;10.2.1&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;eslint-plugin-import-x&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;4.16.2&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;eslint-plugin-perfectionist&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;5.9.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;eslint-plugin-unused-imports&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;4.4.1&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;globals&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;17.5.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;husky&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;9.1.7&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;inquirer&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;9.0.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;is-ci&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;4.1.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;lint-staged&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;16.4.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;pagefind&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;1.5.2&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;prettier&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;3.8.3&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;sass-embedded&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;1.99.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;typescript&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;6.0.3&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;typescript-eslint&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;8.59.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;vitepress&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;2.0.0-alpha.17&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;vitepress-plugin-image-optimize&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;1.5.1&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;vue&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;3.5.33&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;engines&amp;#34;&lt;/span&gt;: {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;node&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;^24.x&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;pnpm&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;11.x&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Lib&lt;/th&gt;&#xA;          &lt;th&gt;Rôle&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;vitepress 2.0.0-alpha.17&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;SSG framework, basé sur Vite&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;@sugarat/theme&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Theme blog tier-1 (templates, sidebar auto, cover images)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;vue 3.5.33&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Requis par VitePress&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;vitepress-plugin-image-optimize&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Optimisation des images au build&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;pagefind 1.5.2&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Moteur de recherche statique (full-text, side-CDN)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;sass-embedded&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Compile SCSS (utilisé par &lt;code&gt;@sugarat/theme&lt;/code&gt;)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Husky / lint-staged / commitlint&lt;/td&gt;&#xA;          &lt;td&gt;Mêmes outils que les autres dépôts (Conventional Commits)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;docsvitepressconfigmts&#34;&gt;&lt;code&gt;docs/.vitepress/config.mts&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#docsvitepressconfigmts&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; { defineConfig } &lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;vitepress&amp;#39;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; path &lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;path&amp;#39;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; fs &lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;fs&amp;#39;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; { generateSkills } &lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;./plugins/skills&amp;#39;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; { generateLlms } &lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;./plugins/llm&amp;#39;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; { generatePub } &lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;./plugins/pub&amp;#39;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; { blogTheme } &lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;./blog-theme&amp;#39;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; webpConfig &lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;./plugins/webp&amp;#39;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; base &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; process.env.GITHUB_ACTIONS &lt;span style=&#34;color:#ff79c6&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;true&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;/ocarina-holy-book/&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;/&amp;#39;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;default&lt;/span&gt; defineConfig({&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  locales&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    root&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      themeConfig&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        outline&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; { label&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;Table of content&amp;#39;&lt;/span&gt; },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        skipToContentLabel&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;Skip to content&amp;#39;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        sidebarMenuLabel&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;Related articles&amp;#39;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        returnToTopLabel&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;Return to top&amp;#39;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        lastUpdatedText&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;Last update:&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      description&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Ocarina is Igor&amp;#39;s automated web browser testing framework.&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      title&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;The Ocarina Holy Book&amp;#39;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      label&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;English&amp;#39;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      lang&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;en&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    fr&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      themeConfig&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        outline&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; { label&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;Sommaire&amp;#39;&lt;/span&gt;, level&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#bd93f9&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#bd93f9&#34;&gt;3&lt;/span&gt;] },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        returnToTopLabel&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;Retour en haut de page&amp;#39;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        lastUpdatedText&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;Dernière mise à jour :&amp;#39;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        skipToContentLabel&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;Passer au contenu&amp;#39;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        sidebarMenuLabel&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;Voir aussi&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      title&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Le livre sacré d&amp;#39;Ocarina&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      label&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;Français&amp;#39;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      lang&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;fr&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ru&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      themeConfig&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        outline&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          label&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;Оглавление&amp;#39;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          level&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#bd93f9&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#bd93f9&#34;&gt;3&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        skipToContentLabel&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;Перейти к содержанию&amp;#39;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        lastUpdatedText&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;Последнее обновление:&amp;#39;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        returnToTopLabel&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;Вернуться вверх&amp;#39;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        sidebarMenuLabel&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;Смотрите также&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      title&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;Священная Книга Ocarina&amp;#39;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      label&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;Русский&amp;#39;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      lang&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;ru&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  vite&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; { ... },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ...&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;1-base&#34;&gt;1. &lt;code&gt;base&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#1-base&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; base &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  process.env.GITHUB_ACTIONS &lt;span style=&#34;color:#ff79c6&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;true&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;/ocarina-holy-book/&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;/&amp;#34;&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;→ En CI (GitHub Actions) : base = &lt;code&gt;/ocarina-holy-book/&lt;/code&gt; (préfixe GitHub Pages).&lt;br&gt;&#xA;→ En local : &lt;code&gt;/&lt;/code&gt; (sert depuis la racine du &lt;em&gt;dev server&lt;/em&gt;).&lt;/p&gt;</description>
    </item>
    <item>
      <title>09.03.01 — Skills Review</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/03-skills/01-review/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/03-skills/01-review/</guid>
      <description>&lt;h1 id=&#34;090301skills-review&#34;&gt;09.03.01 — Skills Review&lt;a class=&#34;anchor&#34; href=&#34;#090301skills-review&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Lectures &lt;strong&gt;statiques&lt;/strong&gt;, remontent des constats. Une grande famille. Permet à l&amp;rsquo;IA d&amp;rsquo;effectuer de la &lt;em&gt;revue&lt;/em&gt; sur son projet de manière systématique.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;listing-potentiellement-non-exhaustif&#34;&gt;Listing (potentiellement non exhaustif)&lt;a class=&#34;anchor&#34; href=&#34;#listing-potentiellement-non-exhaustif&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Skill&lt;/th&gt;&#xA;          &lt;th&gt;Cible&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;review-spec-gaps&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Questions de clarification sur les SFD&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;review-watcher-misuse&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Vérifie le principe « &lt;em&gt;négatif uniquement&lt;/em&gt; » de &lt;code&gt;watcher.report(...)&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;review-compartmentalisation-leaks&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Détecte URLs, sélecteurs, nombres magiques aux mauvais endroits&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;review-dead-code&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Détecte connecteurs / POMs / scénarios / suites / fragments / constantes non utilisés&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;review-report&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Classifie chaque FAIL / SKIP d&amp;rsquo;une exécution&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;review-type-ignore&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Audite les &lt;code&gt;# type: ignore&lt;/code&gt; (sont-ils justifiés ?)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;review-match-candidates&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Identifie les endroits où un &lt;code&gt;match&lt;/code&gt; (Python, &lt;em&gt;pattern matching&lt;/em&gt;) pourrait être utilisé plutôt que des &lt;code&gt;if&lt;/code&gt; &lt;code&gt;elif&lt;/code&gt; &lt;code&gt;elif&lt;/code&gt; &lt;code&gt;if&lt;/code&gt; &lt;code&gt;if&lt;/code&gt; &lt;code&gt;elif&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;review-unverified-transitions&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Vérifie qu&amp;rsquo;à chaque &lt;em&gt;transition de page&lt;/em&gt;, il y a un &lt;code&gt;verify&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;review-submit-dispatchers&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Audite les méthodes de confirmation de saisie (clic vs touche entrée&amp;hellip;)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;review-comment-drift&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Détecte les commentaires qui sont désynchronisés avec le code&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;review-suite-stability&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Évalue la stabilité d&amp;rsquo;une suite (proportion de retries, transient_errors hits)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;review-intent-collisions&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Détecte les tests qui s&amp;rsquo;écrasent mutuellement (intentions contradictoires) et demande/propose des clarifications&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;review-watcher-emissions&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Audite les émissions de watchers (volume, déduplication, pertinence)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;review-hierarchy-naming&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Audite le nommage de la hiérarchie (&lt;code&gt;TestCycle&lt;/code&gt; / &lt;code&gt;TestCampaign&lt;/code&gt; / &lt;code&gt;TestSuite&lt;/code&gt; / &lt;code&gt;Test&lt;/code&gt;) pour repérer l&amp;rsquo;antipattern où un enfant reprend le nom du parent&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;review-dead-code&#34;&gt;&lt;code&gt;review-dead-code&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#review-dead-code&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;input  : base de tests&#xA;output : liste des éléments non utilisés (connectors / POMs / scénarios / fragments / constantes)&#xA;         + recommandation par élément :&#xA;            - supprimer&#xA;            - mettre en incubateur (&amp;lt;racine-source&amp;gt;/incubator/, arbre de dépendances préservé)&#xA;            - conserver (justifier)&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;review-report&#34;&gt;&lt;code&gt;review-report&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#review-report&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;input  : une exécution récente (logs + reports)&#xA;output : classification de chaque test :&#xA;            - PASS                  (rien à faire)&#xA;            - SKIP                  (pourquoi ?)&#xA;            - intentional gap FAIL  (G-DATA-*, G-SEC-*, ...)&#xA;            - cross-browser FAIL    (B-BROWSER-*)&#xA;            - transient FAIL        (A-ENV-*)&#xA;            - régression            ⚠️ ALERTE&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;review-watcher-misuse&#34;&gt;&lt;code&gt;review-watcher-misuse&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#review-watcher-misuse&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;input  : tous les watcher.report(...)&#xA;output : liste des reports qui semblent positifs (« success », « completed », « ok », ...)&#xA;         → recommandation : supprimer ou reformuler en négatif&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;review-comment-drift&#34;&gt;&lt;code&gt;review-comment-drift&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#review-comment-drift&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;input  : tous les commentaires du code&#xA;output : liste des commentaires qui semblent ne plus correspondre au code adjacent&#xA;         (typique : commentaire mentionne foo, code mentionne bar)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Aide à éliminer les commentaires obsolètes.&lt;br&gt;&#xA;« &lt;em&gt;Teach the pattern, not the symptom&lt;/em&gt; ».&lt;/p&gt;</description>
    </item>
    <item>
      <title>10.01 — Matrice récapitulative des workflows</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/01-matrix/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/01-matrix/</guid>
      <description>&lt;h1 id=&#34;1001matrice-récapitulative-des-workflows&#34;&gt;10.01 — Matrice récapitulative des workflows&lt;a class=&#34;anchor&#34; href=&#34;#1001matrice-r%c3%a9capitulative-des-workflows&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Vue d&amp;rsquo;ensemble synthétique de tous les workflows de l&amp;rsquo;écosystème.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;récap&#34;&gt;Récap.&lt;a class=&#34;anchor&#34; href=&#34;#r%c3%a9cap&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Dépôt&lt;/th&gt;&#xA;          &lt;th&gt;Workflow&lt;/th&gt;&#xA;          &lt;th&gt;Trigger&lt;/th&gt;&#xA;          &lt;th&gt;OS&lt;/th&gt;&#xA;          &lt;th&gt;Stack&lt;/th&gt;&#xA;          &lt;th&gt;Effet&lt;/th&gt;&#xA;          &lt;th&gt;Détail&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ocarina&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;main_ci.yml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;push/PR main, dispatch&lt;/td&gt;&#xA;          &lt;td&gt;ubuntu + windows × py 3.14 (option 3.15-dev)&lt;/td&gt;&#xA;          &lt;td&gt;venv cache, make install-on-ci&lt;/td&gt;&#xA;          &lt;td&gt;make check-coding-style + make test + Allure history + Pages deploy&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/02-ocarina-workflows/&#34;&gt;&lt;code&gt;02-ocarina-workflows.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ocarina&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;dev_ci.yml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;push dev/feature/fix, PR dev, dispatch&lt;/td&gt;&#xA;          &lt;td&gt;ubuntu × py 3.14&lt;/td&gt;&#xA;          &lt;td&gt;venv cache&lt;/td&gt;&#xA;          &lt;td&gt;check-coding-style + test + récap couverture (non bloquant)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/02-ocarina-workflows/&#34;&gt;&lt;code&gt;02-ocarina-workflows.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ocarina&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;unstable_python_full_build.yml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;cron (mensuel), dispatch&lt;/td&gt;&#xA;          &lt;td&gt;ubuntu + windows × py &lt;strong&gt;3.15-dev&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;PIP_ONLY_BINARY=&amp;quot;:all:&amp;quot;&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;full build sur Python pré-release&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/02-ocarina-workflows/&#34;&gt;&lt;code&gt;02-ocarina-workflows.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ocarina-example&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;main_ci.yml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;push/PR main, dispatch&lt;/td&gt;&#xA;          &lt;td&gt;ubuntu + windows × py 3.14&lt;/td&gt;&#xA;          &lt;td&gt;venv cache&lt;/td&gt;&#xA;          &lt;td&gt;check-coding-style&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/03-example-workflows/&#34;&gt;&lt;code&gt;03-example-workflows.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ocarina-example&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;dev_ci.yml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;push dev, dispatch&lt;/td&gt;&#xA;          &lt;td&gt;ubuntu × py 3.14&lt;/td&gt;&#xA;          &lt;td&gt;venv cache&lt;/td&gt;&#xA;          &lt;td&gt;check-coding-style&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/03-example-workflows/&#34;&gt;&lt;code&gt;03-example-workflows.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ocarina-example&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;e2e.yml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;dispatch (env &lt;code&gt;OC&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;ubuntu + service &lt;code&gt;redis:8.x&lt;/code&gt; + Firefox + geckodriver 0.35.0&lt;/td&gt;&#xA;          &lt;td&gt;venv cache&lt;/td&gt;&#xA;          &lt;td&gt;run complet, upload traces&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/03-example-workflows/&#34;&gt;&lt;code&gt;03-example-workflows.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ocarina-with-ai-example&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ai_proof_ci.yml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;push main, PR, dispatch&lt;/td&gt;&#xA;          &lt;td&gt;ubuntu × py 3.14&lt;/td&gt;&#xA;          &lt;td&gt;venv cache&lt;/td&gt;&#xA;          &lt;td&gt;ruff format check + ruff check + mypy&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/04-ai-workflows/&#34;&gt;&lt;code&gt;04-ai-workflows.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ocarina-with-ai-example&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ai_proof_e2e.yml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;dispatch&lt;/td&gt;&#xA;          &lt;td&gt;ubuntu × &lt;strong&gt;matrice firefox+chrome&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;venv cache, geckodriver 0.36.0 / chromedriver auto-matché&lt;/td&gt;&#xA;          &lt;td&gt;warm-up Heroku dyno, run, filtrage sed des stacktraces ChromeDriver, upload&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/04-ai-workflows/&#34;&gt;&lt;code&gt;04-ai-workflows.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;igoristan&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ci-pr.yml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;PR &lt;code&gt;**&lt;/code&gt;, dispatch&lt;/td&gt;&#xA;          &lt;td&gt;ubuntu × node 24 + pnpm 11&lt;/td&gt;&#xA;          &lt;td&gt;wireit&lt;/td&gt;&#xA;          &lt;td&gt;format check + lint + typecheck (parallélisés)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/05-igoristan-workflows/&#34;&gt;&lt;code&gt;05-igoristan-workflows.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;igoristan&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;deploy.yml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;push main, dispatch&lt;/td&gt;&#xA;          &lt;td&gt;ubuntu × node 24 + pnpm 11&lt;/td&gt;&#xA;          &lt;td&gt;concurrency group &lt;code&gt;pages&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;pnpm install --frozen-lockfile&lt;/code&gt; + &lt;code&gt;pnpm build&lt;/code&gt; + upload Pages&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/05-igoristan-workflows/&#34;&gt;&lt;code&gt;05-igoristan-workflows.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ocarina-holy-book&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;deploy.yml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;push main, PR, dispatch&lt;/td&gt;&#xA;          &lt;td&gt;ubuntu × node 24 + pnpm 11&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;TZ=Europe/Paris&lt;/code&gt;, concurrency group &lt;code&gt;pages&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;pnpm install&lt;/code&gt; + &lt;code&gt;pnpm build&lt;/code&gt; + upload Pages&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/06-holy-book-workflow/&#34;&gt;&lt;code&gt;06-holy-book-workflow.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;tests-workers&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;aucun&lt;/em&gt;&lt;/td&gt;&#xA;          &lt;td&gt; — &lt;/td&gt;&#xA;          &lt;td&gt; — &lt;/td&gt;&#xA;          &lt;td&gt;Vercel&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;vercel deploy&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/07-tests-workers-vercel/&#34;&gt;&lt;code&gt;07-tests-workers-vercel.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;action-composite&#34;&gt;Action composite&lt;a class=&#34;anchor&#34; href=&#34;#action-composite&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Action&lt;/th&gt;&#xA;          &lt;th&gt;Localisation&lt;/th&gt;&#xA;          &lt;th&gt;Usage&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;allure-history&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ocarina/.github/actions/allure-history/action.yml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Composite : Bun + Allure 3.9.0 + restore/save &lt;code&gt;history.jsonl&lt;/code&gt; sur branche dédiée&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;patterns&#34;&gt;Patterns&lt;a class=&#34;anchor&#34; href=&#34;#patterns&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;workflows-pr-légers-auto&#34;&gt;Workflows PR (légers, auto)&lt;a class=&#34;anchor&#34; href=&#34;#workflows-pr-l%c3%a9gers-auto&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ocarina/main_ci.yml                                       # python 3.14&#xA;ocarina/dev_ci.yml&#xA;ocarina-example/main_ci.yml&#xA;ocarina-example/dev_ci.yml&#xA;ocarina-with-ai-example/ai_proof_ci.yml&#xA;igoristan/ci-pr.yml&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Tous tournent rapidement.&lt;/p&gt;</description>
    </item>
    <item>
      <title>11.01 — Grammaire souveraine</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/11-independence/01-sovereign-grammar/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/11-independence/01-sovereign-grammar/</guid>
      <description>&lt;h1 id=&#34;1101grammaire-souveraine&#34;&gt;11.01 — Grammaire souveraine&lt;a class=&#34;anchor&#34; href=&#34;#1101grammaire-souveraine&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Pas de DSL imposé. Pas d&amp;rsquo;écosystème de plugins. Extension par &lt;strong&gt;composition&lt;/strong&gt;, jamais par héritage.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;vision&#34;&gt;Vision&lt;a class=&#34;anchor&#34; href=&#34;#vision&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Citation du Holy Book (chapitre « &lt;em&gt;Qu&amp;rsquo;est donc Ocarina ?&lt;/em&gt; ») :&lt;/p&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;&lt;em&gt;Et si déléguer sa grammaire à des &amp;ldquo;standards&amp;rdquo; n&amp;rsquo;avait jamais été une bonne idée ?&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;Le vrai hiatus du test E2E &lt;strong&gt;n&amp;rsquo;est pas d&amp;rsquo;imposer la &amp;ldquo;meilleure&amp;rdquo; novlangue&lt;/strong&gt;.&#xA;Réponse simple : Ocarina est &lt;strong&gt;extensible&lt;/strong&gt;.&lt;/p&gt;&#xA;&lt;p&gt;On y crée les verbes et conjonctions que l&amp;rsquo;on veut, le tout régi par des &lt;strong&gt;règles strictes qui poussent l&amp;rsquo;ensemble à rester profondément cohérent&lt;/strong&gt;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>12.01 — Toutes les citations du manifeste, sourcées</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/01-sourced-citations/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/01-sourced-citations/</guid>
      <description>&lt;h1 id=&#34;1201toutes-les-citations-du-manifeste-sourcées&#34;&gt;12.01 — Toutes les citations du manifeste, sourcées&lt;a class=&#34;anchor&#34; href=&#34;#1201toutes-les-citations-du-manifeste-sourc%c3%a9es&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Le Holy Book est truffé de citations dont la provenance n&amp;rsquo;est jamais annoncée dans le texte. Ce sont des mots de passe : si tu les reconnais, tu sais d&amp;rsquo;où vient l&amp;rsquo;auteur ; sinon, tu lis le slogan sans le sous-texte.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;listing&#34;&gt;Listing&lt;a class=&#34;anchor&#34; href=&#34;#listing&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;#&lt;/th&gt;&#xA;          &lt;th&gt;Citation (Holy Book)&lt;/th&gt;&#xA;          &lt;th&gt;Auteur / Source&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;Année&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;1&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;In the Lulzboat, salute, bitch, and show some respect.&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;YTCracker — &lt;code&gt;#antisec&lt;/code&gt;&lt;/strong&gt; (single, theme song officiel d&amp;rsquo;Operation AntiSec). &lt;a href=&#34;https://open.spotify.com/track/6k1HKAkbIHAimbdpf7Pn5J&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Track sur Spotify&lt;/a&gt;, &lt;a href=&#34;https://en.wikipedia.org/wiki/YTCracker&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;page Wikipedia YTCracker&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;&lt;strong&gt;22 juin 2011&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;2&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;THE FUCK YOU THINK THIS IS? You HOLLOW! YOU HOLLOW, YOU UNDERSTAND ME? YOU&amp;rsquo;RE WORTHLESS, YOU&amp;rsquo;RE FUCKING TRASH!&lt;/em&gt; », signé « &lt;em&gt;(btw: RIP, DG descendant…)&lt;/em&gt; », puis prolongé en français : « &lt;em&gt;ON VA TE REMPLACER ET TU RETOURNERAS HARCELER TES PAIRS SUR DOTA ET TE BRANLER SUR DES MACROS VBA COMME UNE MERDE ! […] YOU FUCKED WITH US!&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Yung Innanet (kayos)&lt;/strong&gt; — paroles de &lt;strong&gt;&lt;code&gt;true colors&lt;/code&gt;&lt;/strong&gt;, &lt;a href=&#34;https://soundcloud.com/queed-inc/true-colors&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;SoundCloud — &lt;em&gt;queed-inc/true-colors&lt;/em&gt;&lt;/a&gt;. Sysadmin originel de &lt;a href=&#34;https://en.wikipedia.org/wiki/Vx-underground&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;VX Underground&lt;/a&gt;, &lt;a href=&#34;https://github.com/yunginnanet/yunginnanet&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;profil GitHub&lt;/a&gt;. Décédé le &lt;strong&gt;18 octobre 2025&lt;/strong&gt;. Le &lt;em&gt;DG descendant&lt;/em&gt; désigne &lt;strong&gt;Digital Gangster&lt;/strong&gt;, le forum de YTCracker. Cf. &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/06-yung-innanet-vxug/&#34;&gt;&lt;code&gt;06-yung-innanet-vxug.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt; — &lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;3&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;Of currency minted from factoring digits&lt;/em&gt; » (en français dans le Holy Book : « &lt;em&gt;ces monnaies forgées de la factorisation des nombres&lt;/em&gt; »)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;YTCracker — &lt;code&gt;Cryptoilluminati&lt;/code&gt;&lt;/strong&gt; (album &lt;em&gt;A Side Quest for Fractional Cents&lt;/em&gt;). &lt;a href=&#34;https://soundcloud.com/ytcracker/ytcracker-cryptoilluminati&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;SoundCloud&lt;/a&gt;. La line exacte : « &lt;em&gt;Told you to snap up a modest position / Of currency minted from factoring digits / Which of you listened?&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;2022&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;4&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;We Can Do All What You Can&amp;rsquo;t Do.&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Mantra du collectif indonésien YogyaCarderLink (YCL)&lt;/strong&gt;, un des premiers groupes carding / defacement basé à Yogyakarta. Cf. &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/05-indonesian-hackers/&#34;&gt;&lt;code&gt;05-indonesian-hackers.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;début 2000s&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;5&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;I&amp;rsquo;m the bug you can&amp;rsquo;t kill.&lt;/em&gt; » — Holy Book : « &lt;em&gt;Je suis le bug dont tu ne pourras jamais te débarrasser.&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Non attribuable de façon fiable.&lt;/strong&gt; Idiome de la scène hackers des années 2000s, dans la lignée de « &lt;em&gt;I&amp;rsquo;m the null at the end of the string&lt;/em&gt; » (YTCracker, &lt;em&gt;Nerdlife&lt;/em&gt;, 2006). Cf. &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/03-ytcracker-nerdcore-digital-gangster/&#34;&gt;&lt;code&gt;03-ytcracker-nerdcore-digital-gangster.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;6&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;HACK THE PLANET&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;Slogan emblématique de la scène hackers des années 1990. Origine populaire : film &lt;strong&gt;&lt;em&gt;Hackers&lt;/em&gt;&lt;/strong&gt; (1995, Iain Softley), cri de guerre du personnage &lt;em&gt;Cereal Killer&lt;/em&gt;. Repris par la scène réelle (mailing lists, defaces, conférences).&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;1995 (popularisation)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;7&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;YOU FUCKED WITH US!&lt;/em&gt; » / « &lt;em&gt;Don&amp;rsquo;t fuck with us.&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Renvoi direct au mantra &lt;code&gt;Don&#39;t fuck with us&lt;/code&gt;&lt;/strong&gt;, signature récurrente des pages de deface de &lt;strong&gt;YogyaCarderLink&lt;/strong&gt; et plus largement de la scène carding/defacement des années 2000s. Le passage du &lt;strong&gt;futur/présent&lt;/strong&gt; («&lt;em&gt; don&amp;rsquo;t &lt;/em&gt;», avertissement) au &lt;strong&gt;passé&lt;/strong&gt; (« &lt;em&gt;you fucked&lt;/em&gt; », constat) est le pivot rhétorique : &lt;em&gt;on vous avait prévenu, vous l&amp;rsquo;avez fait quand même&lt;/em&gt;. Cf. &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/05-indonesian-hackers/&#34;&gt;&lt;code&gt;05-indonesian-hackers.md&lt;/code&gt;&lt;/a&gt; et &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/02-lulzsec-lulzboat-antisec/&#34;&gt;&lt;code&gt;02-lulzsec-lulzboat-antisec.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;années 2000s&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;8&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;Putains de skids. Putains de normies !&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;Termes natifs IRC : &lt;strong&gt;&lt;code&gt;skid&lt;/code&gt;&lt;/strong&gt; = abréviation de &lt;em&gt;script kiddie&lt;/em&gt; ; &lt;strong&gt;&lt;code&gt;normie&lt;/code&gt;&lt;/strong&gt; = utilisateur lambda / non-initié. Pas une citation attribuable, c&amp;rsquo;est du EFnet / 2600 / Digital Gangster. Cf. &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/04-zoneh-irc-efnet/&#34;&gt;&lt;code&gt;04-zoneh-irc-efnet.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt; — &lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;9&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;de Zone-H à une vie rangée&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Zone-H&lt;/strong&gt; = archive publique des defaces, fondée le &lt;strong&gt;2 mars 2002&lt;/strong&gt; par Roberto Preatoni (&lt;em&gt;Sys64738&lt;/em&gt;). Cf. &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/04-zoneh-irc-efnet/&#34;&gt;&lt;code&gt;04-zoneh-irc-efnet.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;2002&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;10&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;CODE IS LAW&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Lawrence Lessig&lt;/strong&gt;, &lt;em&gt;Code and Other Laws of Cyberspace&lt;/em&gt;, MIT 1999. Cri d&amp;rsquo;alerte sur la &lt;em&gt;réglementation&lt;/em&gt; par le code. Récupéré comme &lt;em&gt;idéal&lt;/em&gt; par la crypto / Ethereum à partir de 2015.&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;1999 / 2015&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;11&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;Lire aussi : Haters, par Paul Graham.&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;Essai de &lt;strong&gt;Paul Graham&lt;/strong&gt; (co-fondateur Y Combinator), &lt;a href=&#34;https://paulgraham.com/fh.html&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;paulgraham.com/fh.html&lt;/code&gt;&lt;/a&gt;. Sur les commentaires hostiles en ligne.&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt; — &lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;12&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;I won&amp;rsquo;t let you pay me for my open source — Fuck You&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;David Heinemeier Hansson (DHH)&lt;/strong&gt;, créateur de Ruby on Rails. &lt;a href=&#34;https://world.hey.com/dhh/i-won-t-let-you-pay-me-for-my-open-source-d7cf4568&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Post du blog &lt;em&gt;Hey&lt;/em&gt;&lt;/a&gt;.&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt; — &lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;13&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;Retry flaky blocks.&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Yegor Bugayenko&lt;/strong&gt;, &lt;em&gt;Prompt&lt;/em&gt; (&lt;a href=&#34;https://github.com/yegor256/prompt&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;github.com/yegor256/prompt&lt;/code&gt;&lt;/a&gt;). Auteur de &lt;em&gt;Angry Tests&lt;/em&gt;.&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt; — &lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;14&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;Un idiot admire la complexité, un génie admire la simplicité…&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Terry Davis&lt;/strong&gt; (1969–2018), créateur de TempleOS. Considéré par certains comme « &lt;em&gt;le programmeur le plus intelligent qui ait jamais existé&lt;/em&gt; ». Programmeur atteint de schizophrénie et ayant été l&amp;rsquo;une des victimes les plus emblématiques du cyber-harcèlement.&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt; — &lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;15&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;Pour avoir de la connaissance, ajouter des choses chaque jour. Pour avoir de la sagesse, enlever des choses chaque jour.&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Lao-Tseu&lt;/strong&gt;, &lt;em&gt;Tao Te Ching&lt;/em&gt;. Alternative pour ceux qui n&amp;rsquo;osent pas citer Terry A. Davis en raison des polémiques.&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;6e siècle av. J.-C.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;16&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;La perfection est atteinte non pas lorsqu&amp;rsquo;il n&amp;rsquo;y a plus rien à ajouter, mais lorsqu&amp;rsquo;il n&amp;rsquo;y a plus rien à retirer.&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Antoine de Saint-Exupéry&lt;/strong&gt;, &lt;em&gt;Terre des Hommes&lt;/em&gt; (1939).&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;1939&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;17&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;Le style, pour l&amp;rsquo;écrivain aussi bien que pour le peintre, est une question non de technique, mais de vision.&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Marcel Proust&lt;/strong&gt; (entretien).&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt; — &lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;18&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;Pour l&amp;rsquo;instant, vivez les questions…&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Rainer Maria Rilke&lt;/strong&gt;, &lt;em&gt;Lettres à un jeune poète&lt;/em&gt; (1903-1908).&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;1903-1908&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;19&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;La meilleure façon de résoudre un problème est souvent d&amp;rsquo;en sortir.&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;Attribué à &lt;strong&gt;Alan Watts&lt;/strong&gt;.&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt; — &lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;20&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;« Bonne continuation. » / « Peace out. »&lt;/em&gt; → &lt;strong&gt;YTCracker — &lt;em&gt;Robots Will Definitely Take Your Job&lt;/em&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;YTCracker, &lt;a href=&#34;https://soundcloud.com/ytcracker/ytcracker-robots-will-definitely-take-your-job&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;SoundCloud&lt;/a&gt;. Le titre &lt;em&gt;est&lt;/em&gt; la thèse : l&amp;rsquo;IA va remplacer les codeurs incompétents. L&amp;rsquo;expression « &lt;em&gt;Peace out&lt;/em&gt; » est régulièrement utilisé dans cette communauté comme une manière de saluer, avec une certaine non-chalance/froideur.&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt; — &lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;lectures-connexes&#34;&gt;Lectures connexes&lt;a class=&#34;anchor&#34; href=&#34;#lectures-connexes&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Axe &lt;strong&gt;scène hackers&lt;/strong&gt; (1, 7, 9, 11) → &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/02-lulzsec-lulzboat-antisec/&#34;&gt;&lt;code&gt;02-lulzsec-lulzboat-antisec.md&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/04-zoneh-irc-efnet/&#34;&gt;&lt;code&gt;04-zoneh-irc-efnet.md&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;Axe &lt;strong&gt;YTCracker / Nerdcore&lt;/strong&gt; (1, 3, 5, 20) → &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/03-ytcracker-nerdcore-digital-gangster/&#34;&gt;&lt;code&gt;03-ytcracker-nerdcore-digital-gangster.md&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;Axe &lt;strong&gt;indonésien&lt;/strong&gt; (4) → &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/05-indonesian-hackers/&#34;&gt;&lt;code&gt;05-indonesian-hackers.md&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;Axe &lt;strong&gt;Yung Innanet&lt;/strong&gt; (2) → &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/06-yung-innanet-vxug/&#34;&gt;&lt;code&gt;06-yung-innanet-vxug.md&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;Axe &lt;strong&gt;philosophique / sagesse&lt;/strong&gt; (14, 15, 16, 17, 18, 19) → déjà couvert dans &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/01-philosophy/04-citations-and-influences/&#34;&gt;&lt;code&gt;../01-philosophy/04-citations-and-influences.md&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;Axe &lt;strong&gt;politique du code&lt;/strong&gt; (10, 11, 12, 13) → déjà couvert dans &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/01-philosophy/05-political-stance/&#34;&gt;&lt;code&gt;../01-philosophy/05-political-stance.md&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;pourquoi-la-source-compte&#34;&gt;Pourquoi la source compte&lt;a class=&#34;anchor&#34; href=&#34;#pourquoi-la-source-compte&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Le Holy Book &lt;em&gt;ne cite jamais&lt;/em&gt; ses sources. Pas par négligence, par &lt;strong&gt;convention de scène&lt;/strong&gt; : si tu reconnais, c&amp;rsquo;est que tu en fais partie ; si tu ne reconnais pas, tu lis le texte &lt;em&gt;surface&lt;/em&gt;, ce qui marche aussi. C&amp;rsquo;est un dispositif littéraire vieux comme les communautés &lt;em&gt;underground&lt;/em&gt;. LulzSec le faisaient sur leurs pastebins.&lt;/p&gt;</description>
    </item>
    <item>
      <title>99.01 — Glossaire</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/99-references/01-glossary/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/99-references/01-glossary/</guid>
      <description>&lt;h1 id=&#34;9901glossaire&#34;&gt;99.01 — Glossaire&lt;a class=&#34;anchor&#34; href=&#34;#9901glossaire&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Terminologie utilisée dans l&amp;rsquo;écosystème Ocarina. Sources d&amp;rsquo;origine entre parenthèses.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;railway-oriented-programming-rop&#34;&gt;Railway Oriented Programming (ROP)&lt;a class=&#34;anchor&#34; href=&#34;#railway-oriented-programming-rop&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Terme&lt;/th&gt;&#xA;          &lt;th&gt;Définition&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;ROP&lt;/strong&gt; (Wlaschin, F#)&lt;/td&gt;&#xA;          &lt;td&gt;Pattern fonctionnel : succès et échec sont deux &lt;em&gt;rails&lt;/em&gt; parallèles ; un échec bascule le calcul sur le rail d&amp;rsquo;échec, où il reste (court-circuit).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;Result[T]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Union discriminée &lt;code&gt;Ok[T] | Fail&lt;/code&gt;. Erreur portée comme &lt;strong&gt;valeur&lt;/strong&gt;, pas comme exception levée.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;Ok[T]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Wrapper de succès, &lt;code&gt;value: T&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;Fail&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Wrapper d&amp;rsquo;échec, &lt;code&gt;error: Exception&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;is_ok&lt;/code&gt;, &lt;code&gt;is_fail&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;TypeGuard&lt;/code&gt; qui narrowent un &lt;code&gt;Result[T]&lt;/code&gt; vers &lt;code&gt;Ok[T]&lt;/code&gt; ou &lt;code&gt;Fail&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;Action[T]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Thunk[Result[T]]&lt;/code&gt; — fonction sans argument qui rend un &lt;code&gt;Result[T]&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;ActionStart[T]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;État initial du builder d&amp;rsquo;action ROP. Attend &lt;code&gt;.failure(...)&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;ActionFailure[T]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Après &lt;code&gt;.failure(...)&lt;/code&gt;. Attend &lt;code&gt;.success(...)&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;ActionSuccess[T]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Après &lt;code&gt;.success(...)&lt;/code&gt;. Attend &lt;code&gt;.execute()&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;ActionChain[T]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Après &lt;code&gt;.execute()&lt;/code&gt;. Porte &lt;code&gt;has_failed: bool&lt;/code&gt; et &lt;code&gt;result: Result[T]&lt;/code&gt;. Permet &lt;code&gt;.then(...)&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;NeutralActionStart[T]&lt;/code&gt;, &lt;code&gt;NeutralActionFailure[T]&lt;/code&gt;, &lt;code&gt;NeutralActionSuccess[T]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;États &lt;em&gt;inertes&lt;/em&gt; du rail d&amp;rsquo;échec. Bouchonnent en ne faisant plus rien.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;ChainRunner[T]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Encapsulation paresseuse d&amp;rsquo;un &lt;code&gt;ActionChain&lt;/code&gt;. Exécutée par &lt;code&gt;.run()&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;drive_page&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Alias sémantique opinionated pour &lt;code&gt;chain_actions&lt;/code&gt;. « &lt;em&gt;Prendre le contrôle d&amp;rsquo;une page&lt;/em&gt; ».&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;match_page&lt;/code&gt; / &lt;code&gt;when&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Branchement conditionnel. First-match wins.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;create_act&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Primitive de bas niveau pour créer &lt;code&gt;act&lt;/code&gt; (&lt;em&gt;pas de test&lt;/em&gt;). Wrappée par le projet utilisateur.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;act&lt;/code&gt;&lt;/strong&gt; (côté projet)&lt;/td&gt;&#xA;          &lt;td&gt;Wrapper utilisateur de &lt;code&gt;create_act&lt;/code&gt; avec hook &lt;code&gt;on_failure&lt;/code&gt; spécifique au projet. Permet de représenter un &lt;em&gt;pas de test&lt;/em&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;istqb-vocabulaire-professionnels-du-test&#34;&gt;ISTQB / vocabulaire professionnels du test&lt;a class=&#34;anchor&#34; href=&#34;#istqb-vocabulaire-professionnels-du-test&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Terme&lt;/th&gt;&#xA;          &lt;th&gt;Définition&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;ISTQB&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;International Software Testing Qualifications Board. Organisme professionnel du test logiciel.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Test cycle&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Niveau le plus haut. Une exécution complète.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Test campaign&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Niveau sous le cycle. Non défini directement dans l&amp;rsquo;ISTQB mais reconnu dans le vocabulaire des testeurs de métier.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Test suite&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Niveau sous la campagne. Une collection de tests &lt;em&gt;exécutables&lt;/em&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Test case&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Un cas de test. Unité atomique.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Test step&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Un pas de test. Action atomique dans un test case.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Smoke test&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Vérification minimale qui prouve que le système est &lt;em&gt;en marche&lt;/em&gt;. Si échec, le reste est inutile.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Setup / Teardown&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Pré-condition / post-condition d&amp;rsquo;un test.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;ocarina-orchestration&#34;&gt;Ocarina (orchestration)&lt;a class=&#34;anchor&#34; href=&#34;#ocarina-orchestration&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Terme&lt;/th&gt;&#xA;          &lt;th&gt;Définition&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;Test[Driver]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Test ISTQB. Métadonnées + scenario factory + fragments + skip.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;TestSuite[Driver]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Suite ISTQB. Parallélisation (&lt;em&gt;thread pool&lt;/em&gt;), saturation, filtrage IDs, retries.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;TestCampaign[Driver]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Campagne. Séquence de suites avec config workers partagée.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;TestCycle[Driver]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Cycle ISTQB. Smoke (avec modes &lt;code&gt;fail-fast&lt;/code&gt; vs &lt;code&gt;wait-for-all&lt;/code&gt;) + main.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;TestExecutor[Driver]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Exécute une seule tentative d&amp;rsquo;un test. Stateless.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;TestFlow[Driver]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Boucle de rejeu (1+max_retries) avec backoff linéaire.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;TestRunner[Driver]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Sortie de &lt;code&gt;Test.spawn(driver, logger)&lt;/code&gt;. Agrégateur de chain_runners + setup + teardown + watchers.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;Scenario[Driver]&lt;/code&gt;&lt;/strong&gt; (frozen dataclass)&lt;/td&gt;&#xA;          &lt;td&gt;Encapsule &lt;code&gt;test_chain&lt;/code&gt;, &lt;code&gt;setup&lt;/code&gt;, &lt;code&gt;teardown&lt;/code&gt;, &lt;code&gt;watchers&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;TestChain&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Sequence[ChainRunner[Any]]&lt;/code&gt;. La séquence à exécuter pour appliquer le test.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;Watcher[Driver]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Daemon thread&lt;/em&gt; pour observer quelque chose en plus sur un test. Callback périodique.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;Saturation&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Mécanisme de clonage aléatoire de tests pour atteindre &lt;code&gt;max_workers&lt;/code&gt;. Copies nommées &lt;code&gt;[COPY N] &amp;lt;name&amp;gt;&lt;/code&gt; (ou &lt;code&gt;[+N]&lt;/code&gt; selon convention).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;Transient errors&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Tuple d&amp;rsquo;exceptions qui déclenchent un rejeu.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;max_retries_per_test&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Nombre de rejeux. Par défaut, 8 (« &lt;em&gt;9 vies comme un chat&lt;/em&gt; 🐱 »).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;autoscreen_on_fail&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Capture automatique de captures d&amp;rsquo;écran lors d&amp;rsquo;un pas de test en échec.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;ocarina-dsl-invariants&#34;&gt;Ocarina (DSL invariants)&lt;a class=&#34;anchor&#34; href=&#34;#ocarina-dsl-invariants&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Terme&lt;/th&gt;&#xA;          &lt;th&gt;Définition&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;validate(value, name=&amp;quot;...&amp;quot;)&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Entry-point du DSL d&amp;rsquo;invariants.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;ValidationStartBlock[T]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;État initial. Attend &lt;code&gt;.assert_that(...)&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;ValidationAssertBlock[T]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Après &lt;code&gt;.assert_that(...)&lt;/code&gt;. Accepte &lt;code&gt;.assert_that&lt;/code&gt;, &lt;code&gt;.otherwise&lt;/code&gt;, &lt;code&gt;.then&lt;/code&gt;, &lt;code&gt;.execute&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;.assert_that(predicate)&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Ajoute un AND logique.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;.otherwise(predicate)&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Ajoute un OR logique. Combine via &lt;code&gt;_any_of&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;.then(new_value)&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Change la valeur en cours dans la chaîne. Conserve les erreurs accumulées.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;.execute()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Exécute la chaîne, retourne un &lt;code&gt;_ValidationResult&lt;/code&gt; (inerte tant qu&amp;rsquo;on n&amp;rsquo;appelle pas &lt;code&gt;.raise_if_invalid()&lt;/code&gt;).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;chain_validations(*)&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Merge plusieurs &lt;code&gt;ValidationAssertBlock&lt;/code&gt; en un seul.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;InvariantViolationError&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Exception levée par les prédicats.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;AggregateInvariantViolationError&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Agrégation de plusieurs &lt;code&gt;InvariantViolationError&lt;/code&gt;. Levée par &lt;code&gt;.raise_if_invalid()&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;DuplicatesError&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Sous-classe d&amp;rsquo;&lt;code&gt;InvariantViolationError&lt;/code&gt; levée par &lt;code&gt;has_unique_elements&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;BusinessInvariantValidator.create&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Factory pour invariants métier.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;FrameworkInvariantValidator.create&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Factory pour invariants framework.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;ocarina-infra--ports&#34;&gt;Ocarina (infra + ports)&lt;a class=&#34;anchor&#34; href=&#34;#ocarina-infra--ports&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Terme&lt;/th&gt;&#xA;          &lt;th&gt;Définition&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;POMBase&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;ABC framework-agnostic. Méthodes &lt;code&gt;verify&lt;/code&gt; + &lt;code&gt;get_current_title&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;SeleniumTitleMixin&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Mixin Selenium-specific. Implémente &lt;code&gt;get_current_title&lt;/code&gt; et type &lt;code&gt;_driver: WebDriver&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;WebDriversPool[Driver]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Pool thread-safe de drivers. Sémaphore + Queue (&lt;em&gt;la QUEUE putain !&lt;/em&gt;) + warmup avec watchdog.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;WarmupTimeoutError&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Levée par &lt;code&gt;pool.warmup()&lt;/code&gt; quand ça coince.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;DriverBuilder[Driver]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Builder qui gère le profile tmp dir et produit &lt;code&gt;(driver, dispose)&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;BuiltWebDriver[Driver]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;tuple[Driver, Effect]&lt;/code&gt; — driver + dispose.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;Screenshotter[TDriver]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Utilitaire de capture thread-safe. Burst mode, healthcheck.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;ScreenshotterConfig[TDriver]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Config (frozen dataclass) : output_dir, file_ext, health_check, save_full_page, etc.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;ActCounter&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Interface. Compte les &lt;code&gt;act&lt;/code&gt; appelés par tentative.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;ThreadsBasedActCounter&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Implémentation thread-local d&amp;rsquo;&lt;code&gt;ActCounter&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;ILogger&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Port (ABC) de logging. Méthodes : &lt;code&gt;debug&lt;/code&gt;, &lt;code&gt;info&lt;/code&gt;, &lt;code&gt;warning&lt;/code&gt;, &lt;code&gt;error&lt;/code&gt;, &lt;code&gt;critical&lt;/code&gt;, &lt;code&gt;success&lt;/code&gt;, &lt;code&gt;test_name&lt;/code&gt;, &lt;code&gt;exception&lt;/code&gt;, &lt;code&gt;raw&lt;/code&gt;, &lt;code&gt;set_prefix&lt;/code&gt;, &lt;code&gt;set_domain_taxonomy&lt;/code&gt;, &lt;code&gt;cleanup&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;ITakeScreenshot[Driver]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Protocol &lt;code&gt;(driver, logger, category) -&amp;gt; None&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;PrintLogger&lt;/code&gt; / &lt;code&gt;FileLogger&lt;/code&gt; / &lt;code&gt;PrintAndFileLogger&lt;/code&gt; / &lt;code&gt;MutedLogger&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;4 implémentations canoniques d&amp;rsquo;&lt;code&gt;ILogger&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;driver_healthcheck(driver)&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Ping &lt;code&gt;driver.title&lt;/code&gt; ; lève &lt;code&gt;DriverDiedError&lt;/code&gt; si LE DRIVER EST DEAD.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;DriverDiedError&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Exception levée par &lt;code&gt;driver_healthcheck&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;PageVerificationError&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Exception levée par &lt;code&gt;POM.verify&lt;/code&gt; quand la page n&amp;rsquo;est pas la bonne.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;NoMatchingBranchError&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Exception levée par &lt;code&gt;match_page&lt;/code&gt; quand aucun &lt;code&gt;when&lt;/code&gt; ne match.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;ocarina-opinionated&#34;&gt;Ocarina (opinionated)&lt;a class=&#34;anchor&#34; href=&#34;#ocarina-opinionated&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Terme&lt;/th&gt;&#xA;          &lt;th&gt;Définition&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;CliBuilder&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Builder déclaratif au-dessus d&amp;rsquo;argparse. Agrégation des erreurs.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;CliArg&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Déclaration d&amp;rsquo;un argument CLI.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;CliStore[TKeys]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Store write-once des valeurs CLI parsées. &lt;code&gt;TKeys: Literal[...]&lt;/code&gt; pour autocomplétion.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;_CliField[T]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Field write-once avec validator.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;phantom_validate&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;No-op validator pour les fields qui n&amp;rsquo;ont pas besoin de validation supplémentaire.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;CliStoreSingleton&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Singleton autour de &lt;code&gt;CliStore&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;create_selenium_auto_cli_store()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Factory qui dispatch par OS.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;bootstrap(test_cycle, run_plugins, post_exec)&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Point d&amp;rsquo;entrée canonique.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;run_plugins(*plugins, exceptions_logger)&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Exécute les plugins en parallèle si N&amp;gt;1.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;pretty_print_results&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Plugin d&amp;rsquo;affichage des résultats dans le terminal (ANSI).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;generate_docx_proof&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Plugin de créations de preuves de test au format DOCX.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;generate_json_results&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Plugin qui retranscrit les mêmes résultats que &lt;code&gt;pretty_print_results&lt;/code&gt;, mais au format JSON.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;timing&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Context manager qui mesure la durée.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;programmation-fonctionnelle&#34;&gt;Programmation fonctionnelle&lt;a class=&#34;anchor&#34; href=&#34;#programmation-fonctionnelle&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Terme&lt;/th&gt;&#xA;          &lt;th&gt;Définition&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;Effect&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Callable[[], None]&lt;/code&gt; — fonction sans argument ni retour, déférée.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;Thunk[T]&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Callable[[], T]&lt;/code&gt; — fonction sans argument, déférée.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;Closure&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Fonction qui capture un environnement.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;Fold&lt;/code&gt; / &lt;code&gt;reduce&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Réduction d&amp;rsquo;une séquence à une seule valeur (&lt;em&gt;catamorphisme&lt;/em&gt;).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;TypeGuard&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;PEP 647 — annotation qui &lt;em&gt;narrow&lt;/em&gt; un type côté &lt;em&gt;type checker&lt;/em&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;Discriminated union&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Union de types disjoints, discriminée par un test (typiquement &lt;code&gt;isinstance&lt;/code&gt;).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;Sealed union&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Discriminated union dont tous les variants sont &lt;code&gt;@final&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;@final&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;PEP 591 — annonce qu&amp;rsquo;une classe ne peut pas être sous-classée (pas de covariance infinie).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;Self&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;PEP 673 — type de retour qui réfère à la classe instanciée (pas la classe déclarée).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;Protocol&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;PEP 544 — type structurel ; tout objet avec la bonne shape est utilisable.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;PEP 695&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Generics syntax &lt;code&gt;class Foo[T]:&lt;/code&gt; (Python 3.12+).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;vocabulaire-de-lauteur&#34;&gt;Vocabulaire de l&amp;rsquo;auteur&lt;a class=&#34;anchor&#34; href=&#34;#vocabulaire-de-lauteur&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Terme&lt;/th&gt;&#xA;          &lt;th&gt;Sens&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Slipologue&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Néologisme. Celui qui prétend « &lt;em&gt;tout savoir&lt;/em&gt; » sans pratique. Pollue les discussions techniques avec des &lt;em&gt;vues de l&amp;rsquo;esprit&lt;/em&gt;. Nous casse les couilles à utiliser ChatGPT ou Wikipédia pour toujours avoir réponse à tout en faisant comme s&amp;rsquo;il parlait d&amp;rsquo;un sujet qu&amp;rsquo;il maîtrise alors que non.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Vues de l&amp;rsquo;esprit&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Concept imaginaire ; vision théorique inapplicable.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Boîte blanche&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Code auditable, lisible, sans logique cachée. Opposé à &lt;em&gt;boîte noire&lt;/em&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Donnée brute&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Code source, fichiers, formats simples.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Astrologie de la programmation&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Patterns ésotériques sans bénéfice mesurable.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;In the Lulzboat, salute, bitch, and show some respect.&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Citation de &lt;strong&gt;YTCracker — &lt;code&gt;#antisec&lt;/code&gt;&lt;/strong&gt; (2011), hymne officiel de &lt;em&gt;l&amp;rsquo;Operation AntiSec&lt;/em&gt;. Voir &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/01-sourced-citations/&#34;&gt;&lt;code&gt;../12-manifesto-deciphered/01-sourced-citations.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Putains de skids, putains de normies&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Marqueur identitaire IRC / EFnet.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;scène-hacker-chapitre-12&#34;&gt;Scène hacker (chapitre 12)&lt;a class=&#34;anchor&#34; href=&#34;#sc%c3%a8ne-hacker-chapitre-12&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Terme&lt;/th&gt;&#xA;          &lt;th&gt;Définition&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;AntiSec movement (1999)&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Mouvement opposé à l&amp;rsquo;industrie de la cybersécurité et à la &lt;em&gt;full disclosure&lt;/em&gt;. Refus de publier exploits / vulnérabilités. Cibles : SecurityFocus, Packet Storm, mailing list Bugtraq, etc.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Operation AntiSec (2011)&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Résurrection du mouvement par LulzSec + Anonymous. 50 jours de hacks (mai-juin 2011).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;LulzSec&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Groupe d&amp;rsquo;hackers actif mai-juin 2011. &lt;em&gt;Splinter&lt;/em&gt; d&amp;rsquo;Anonymous. Manifeste « &lt;em&gt;we do it for the lulz&lt;/em&gt; ». Arrestations en 2012.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;The Lulzboat&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Métaphore identitaire de LulzSec. ASCII art de bateau pirate signant leurs communiqués.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Sabu&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Hector Monsegur, leader LulzSec, soulevé par le FBI en juin 2011.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Topiary&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Jake Davis, spokesperson LulzSec.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;YTCracker&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Bryce Case Jr. (né 1982). Rappeur &lt;em&gt;nerdcore&lt;/em&gt;, ancien black hat (NASA Goddard 1999), fondateur de &lt;em&gt;Digital Gangster&lt;/em&gt; (forum, 2005).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Nerdcore&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Sous-genre du hip-hop dont les paroles sont &lt;em&gt;réellement&lt;/em&gt; pour les nerds (code, hacking, sci-fi, math). Inventé par MC Frontalot (2000).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Digital Gangster&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Forum hacker fondé par YTCracker en 2005 (&lt;code&gt;digitalgangster.com&lt;/code&gt;). 36 000 membres au pic. Source de plusieurs hacks emblématiques (Paris Hilton, Miley Cyrus, Twitter Obama, etc.).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;NerdRap Entertainment System&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Album fondateur de YTCracker (2005). Beats sur OSTs NES, lyrics Nerdcore.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Darknet Diaries&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Podcast de &lt;strong&gt;Jack Rhysider&lt;/strong&gt; sur l&amp;rsquo;histoire de la cybersécurité. &lt;strong&gt;L&amp;rsquo;épisode 78 (&lt;em&gt;Nerdcore&lt;/em&gt;)&lt;/strong&gt; est consacré à YTCracker. Transcript : &lt;a href=&#34;https://darknetdiaries.com/transcript/78/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;https://darknetdiaries.com/transcript/78/&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Zone-H&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Archive publique des defaces de sites web (fondée 2 mars 2002 par Roberto Preatoni / Sys64738, basée en Estonie). Tableau d&amp;rsquo;affichage de la scène grey hat 2002-2015.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;IRC&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Internet Relay Chat&lt;/em&gt; (1988). Protocole de chat textuel multi-utilisateurs en client/serveur. Médium social central des hackers 1993-2012.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;EFnet&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Eris-Free Network&lt;/em&gt;. Réseau IRC historique (1990). Centre de la scène &lt;em&gt;warez&lt;/em&gt;, &lt;em&gt;0day&lt;/em&gt;, &lt;em&gt;phreakers&lt;/em&gt; 1996-2008.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Channel&lt;/strong&gt; (&lt;code&gt;#name&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;Salon IRC.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;skid&lt;/code&gt; / &lt;em&gt;script kiddie&lt;/em&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Hacker qui exécute des scripts qu&amp;rsquo;il ne comprend pas. Insulte.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;normie&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Utilisateur lambda, non-initié. Insulte.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;lamer&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Variante BBS-era de &lt;em&gt;skid&lt;/em&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;gr33tz&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Greetings&lt;/em&gt;, liste du crew, en bas d&amp;rsquo;un deface.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;zine&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;E-zine&lt;/em&gt;, magazine &lt;em&gt;underground&lt;/em&gt; souvent ASCII.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;VX Underground (VXUG)&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Repository public de samples de malware (fondé ~2019 par smelly_vx). Sysadmin originel : &lt;strong&gt;Yung Innanet / kayos&lt;/strong&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Yung Innanet / kayos&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Sysadmin originel de VX Underground et musicien. Décédé le 18 octobre 2025. Le Holy Book cite &lt;strong&gt;directement&lt;/strong&gt; les paroles de son morceau &lt;strong&gt;&lt;code&gt;true colors&lt;/code&gt;&lt;/strong&gt; (&lt;a href=&#34;https://soundcloud.com/queed-inc/true-colors&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;SoundCloud&lt;/a&gt;), signé « &lt;em&gt;(btw: RIP, DG descendant…)&lt;/em&gt; ».&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;YogyaCarderLink (YCL)&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Collectif d&amp;rsquo;hackers indonésiens (Yogyakarta), début 2000s. Deux mantras récurrents sur leurs defaces : « &lt;em&gt;We Can Do All What You Can&amp;rsquo;t Do.&lt;/em&gt; » et &lt;strong&gt;&lt;code&gt;Don&#39;t fuck with us.&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Honker (红客)&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Red hacker&lt;/em&gt; chinois. Hacker patriote, motivé idéologiquement. Apparu après les émeutes Indonésie 1998.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Vilains but not Monsters&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Distinction morale au cœur de la scène hackers : un &lt;em&gt;Vilain&lt;/em&gt; confronte le pouvoir, un &lt;em&gt;Monster&lt;/em&gt; persécute des faibles. Les véritables passionnés détestent les &lt;em&gt;monsters&lt;/em&gt; (qui sont souvent d&amp;rsquo;anciens harcelés reproduisant le mal subi).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;DEF CON&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Conférence hacker annuelle à présent à Las Vegas (autrefois au &lt;em&gt;Caesars Forum&lt;/em&gt;).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;BOFH (Bastard Operator From Hell)&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Série de fiction par &lt;strong&gt;Simon Travaglia&lt;/strong&gt; (Nouvelle-Zélande), publiée sur Usenet dans les années 90 puis sur &lt;em&gt;The Register&lt;/em&gt;. Archétype du sysadmin qui méprise ses utilisateurs. Source du vocabulaire &lt;em&gt;PEBKAC&lt;/em&gt;, &lt;em&gt;RTFM&lt;/em&gt;, &lt;em&gt;ID-10-T error&lt;/em&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Usenet&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Système distribué de &lt;em&gt;newsgroups&lt;/em&gt;. Médium fondateur de la culture hacker.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Shodan&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Moteur de recherche pour &lt;em&gt;devices&lt;/em&gt; connectés à internet (caméras IP, ICS, IoT, serveurs ouverts). Fondé par &lt;strong&gt;John Matherly&lt;/strong&gt;. &lt;a href=&#34;https://shodan.io&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;shodan.io&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;ViolVocal&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Communauté francophone (mi-2000s) de &lt;em&gt;prank callers&lt;/em&gt; / harceleurs téléphoniques organisés sur forum + serveur Mumble/TeamSpeak. Pratique du SIP spoofing, &lt;em&gt;streams&lt;/em&gt; publics des appels, doxing, usurpations d&amp;rsquo;agents de police pour consulter des TAJ sans habilitation, etc. Fermé après poursuites. Cas francophone de la cruauté &lt;em&gt;underground&lt;/em&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;4chan / /b/&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Imageboard fondé par moot (Christopher Poole) en 2003. /b/ = board sans règles, sans archive, anonymat total. Matrice d&amp;rsquo;Anonymous (2006-2008) et de la culture meme moderne. Communauté orchestrant régulièrement du harcèlement, ayant particulièrement dégradé la santé de Terry A. Davis.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Anonymous&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Entité collective émergée de 4chan /b/ vers 2006-2008. Op Chanology (Scientologie 2008), Op Tunisia (2011), Op Sony (2011). Coordination IRC.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Doxing&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Publication d&amp;rsquo;informations personnelles d&amp;rsquo;une cible dans le but de lui nuire.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Swatting&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Faux appel d&amp;rsquo;urgence (souvent SIP-spoofed) déclarant un crime grave à l&amp;rsquo;adresse de la victime, déclenchant l&amp;rsquo;envoi du SWAT. Cas mortels documentés.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;OSINT&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Open Source Intelligence&lt;/em&gt;. Collecte d&amp;rsquo;informations à partir de sources publiques.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;OPSEC&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Operations Security&lt;/em&gt;. Maintien rigoureux de l&amp;rsquo;anonymat opérationnel sur la durée.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Honeypot&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Faux serveur / faux compte / faux leak appâtant les attaquants pour les détecter. Outil défensif de la scène.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Snitch / Fed&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Poucave&lt;/em&gt; / &lt;em&gt;agent fédéral undercover&lt;/em&gt;. Détection prioritaire dans toute communauté &lt;em&gt;underground&lt;/em&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Prompt injection&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Insertion de contenu malveillant dans le contexte d&amp;rsquo;un LLM pour détourner son comportement. OWASP LLM01:2025. Premier rang du Top 10 LLM.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;MCP (Model Context Protocol)&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Protocole standard Anthropic (2024) pour exposer &lt;em&gt;tools&lt;/em&gt; et &lt;em&gt;resources&lt;/em&gt; à un LLM. Chaque MCP server tiers ajoute une surface d&amp;rsquo;attaque.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Hard-won rules&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Conventions internes à un projet documentées dans &lt;code&gt;CLAUDE.md&lt;/code&gt; après leçons coûteuses. Évite aux LLMs de répéter les erreurs déjà rencontrées.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;idéologie-ocarina&#34;&gt;Idéologie Ocarina&lt;a class=&#34;anchor&#34; href=&#34;#id%c3%a9ologie-ocarina&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Terme&lt;/th&gt;&#xA;          &lt;th&gt;Définition&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Vilain (au sens hacker)&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Identité revendiquée du hacker souverain qui agit hors institutions, par opposition aux conventions et au &lt;em&gt;branding corporate&lt;/em&gt;. Le Vilain confronte le pouvoir mais respecte les plus faibles.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Monster (au sens hacker)&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Celui qui exploite techniquement sa compétence pour persécuter des personnes vulnérables (harceleur, doxeur, etc.). &lt;em&gt;Ennemi&lt;/em&gt; structurel de la vraie scène hackerss.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Culture du tooling&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Conviction que les bons outils techniques (Nmap, Wireshark, Metasploit, Burp, sqlmap, &lt;em&gt;et al.&lt;/em&gt;) bien écrits par des petites équipes valent mille présentations marketing.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Tribal loyalty (scène hackers)&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Loyauté absolue intra-communauté + méfiance extrême envers l&amp;rsquo;extérieur. Tests d&amp;rsquo;admission longs, &lt;em&gt;vouching&lt;/em&gt; par membres établis, excommunication brutale en cas de trahison.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Eyes everywhere&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Hyper-vigilance opérationnelle apprise dans le contexte &lt;em&gt;underground&lt;/em&gt; (admin IRC légalement responsable des actions de ses canaux).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;industrie-contre-modèles&#34;&gt;Industrie / contre-modèles&lt;a class=&#34;anchor&#34; href=&#34;#industrie-contre-mod%c3%a8les&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Terme&lt;/th&gt;&#xA;          &lt;th&gt;Définition&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Infopreneur&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Entrepreneur dont le produit est de l&amp;rsquo;information vendue : formations, e-books, masterminds. Modèle américain (Dan Kennedy, Russell Brunson), importé en France via Tugan Bara.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Copywriting&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Art d&amp;rsquo;écrire des &lt;em&gt;textes de vente&lt;/em&gt; persuasifs. Cœur de métier infopreneur. Largement automatisable par IA depuis 2023.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Funnel&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Tunnel de vente&lt;/em&gt; : contenu gratuit → produit front-end (97€) → produit phare (997-4997€) → coaching premium.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Marketing Underground&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Programme phare de Tugan Bara (2018-2023). Posture « &lt;em&gt;bad guy&lt;/em&gt; » assumée.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Tugan Bara / Arnaud Labossière&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Archétype français du modèle infopreneur 2018-2024. HEC dropout. Vendu Tugan.ai en 2024. Finalement le seul infopreneur qui a vraiment expliqué la profession à toute une communauté de la scène francophone.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;MRR&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Monthly Recurring Revenue&lt;/em&gt;. Somme mensuelle facturée aux clients sous abonnement. Indicateur SaaS central.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;ARR&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Annual Recurring Revenue&lt;/em&gt;. Somme annuelle récoltée par la vente d&amp;rsquo;un SaaS.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;ia-ml-rl&#34;&gt;IA, ML, RL&lt;a class=&#34;anchor&#34; href=&#34;#ia-ml-rl&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Terme&lt;/th&gt;&#xA;          &lt;th&gt;Définition&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;LLM&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Large Language Model&lt;/em&gt;. Modèle Transformer pré-entraîné sur un large corpus. Claude, GPT, Gemini, Llama, Mistral.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Transformer&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Architecture introduite par &lt;em&gt;Attention Is All You Need&lt;/em&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Foundation model&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Modèle pré-entraîné réutilisé pour de multiples tâches via &lt;em&gt;fine-tuning&lt;/em&gt; ou &lt;em&gt;prompting&lt;/em&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;RL (Reinforcement Learning)&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Apprentissage par interaction avec un environnement, guidée par un &lt;em&gt;signal de reward&lt;/em&gt;. Sutton &amp;amp; Barto 1998.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Constitutional AI (CAI)&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Méthode d&amp;rsquo;alignement Anthropic (Bai et al., 2022). Le modèle critique et révise ses propres réponses selon une &lt;em&gt;constitution&lt;/em&gt; écrite.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Probe&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Primitive d&amp;rsquo;apprentissage empirique pour l&amp;rsquo;IA.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;llms.txt, llms-full.txt&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Standard Jeremy Howard (fast.ai), 2024. Index Markdown court (&lt;code&gt;llms.txt&lt;/code&gt;) + doc full concaténée (&lt;code&gt;llms-full.txt&lt;/code&gt;) pour ingestion par agent.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;SKILL.md&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Permet à un agent IA de plugger une compétence projet.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;CLAUDE.md&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Donne ensemble de règles à Claude Code.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;</description>
    </item>
    <item>
      <title>00.02 — Matrice stack / responsabilité / licence</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/00-big-picture/02-stack-matrix/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/00-big-picture/02-stack-matrix/</guid>
      <description>&lt;h1 id=&#34;0002matrice-stack-responsabilité-licence&#34;&gt;00.02 — Matrice &lt;em&gt;stack / responsabilité / licence&lt;/em&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0002matrice-stack-responsabilit%c3%a9-licence&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Dépôt&lt;/th&gt;&#xA;          &lt;th&gt;Rôle&lt;/th&gt;&#xA;          &lt;th&gt;Langage&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;Version&lt;/th&gt;&#xA;          &lt;th&gt;Stack principale&lt;/th&gt;&#xA;          &lt;th&gt;Stack outillage&lt;/th&gt;&#xA;          &lt;th&gt;Distribution&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;Licence&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ocarina&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Framework de test e2e navigateur&lt;/td&gt;&#xA;          &lt;td&gt;Python&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;&lt;strong&gt;3.14+&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;hatchling&lt;/code&gt;, &lt;code&gt;python-docx&lt;/code&gt; (seule dépendance d&amp;rsquo;exécution), &lt;code&gt;selenium&lt;/code&gt; + &lt;code&gt;playwright&lt;/code&gt; (dev seulement)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ruff&lt;/code&gt; (ALL), &lt;code&gt;mypy&lt;/code&gt; strict, &lt;code&gt;pytest&lt;/code&gt;, &lt;code&gt;hypothesis&lt;/code&gt;, &lt;code&gt;pytest-mypy-plugins&lt;/code&gt;, &lt;code&gt;syrupy&lt;/code&gt;, &lt;code&gt;prysk&lt;/code&gt; (cram), &lt;code&gt;allure-pytest&lt;/code&gt;, &lt;code&gt;pre-commit&lt;/code&gt;, &lt;code&gt;twine&lt;/code&gt;, &lt;code&gt;build&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;PyPI, v&lt;strong&gt;1.1.10&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;MIT&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ocarina-example&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Suite canonique e2e contre l&amp;rsquo;Igoristan&lt;/td&gt;&#xA;          &lt;td&gt;Python&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;&lt;strong&gt;3.14+&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ocarina&lt;/code&gt;, &lt;code&gt;selenium ≥ 4.40&lt;/code&gt;, &lt;code&gt;python-dotenv&lt;/code&gt;, &lt;code&gt;dogpile.cache&lt;/code&gt;, &lt;code&gt;redis&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ruff&lt;/code&gt;, &lt;code&gt;mypy&lt;/code&gt;, &lt;code&gt;pre-commit&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Source uniquement&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;MIT&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ocarina-with-ai-example&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Suite e2e CURA Healthcare co-écrite par Claude Code&lt;/td&gt;&#xA;          &lt;td&gt;Python&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;&lt;strong&gt;3.14+&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ocarina ≥ 1.0.3&lt;/code&gt;, &lt;code&gt;selenium ≥ 4.40&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ruff&lt;/code&gt;, &lt;code&gt;mypy&lt;/code&gt;, &lt;code&gt;mypy-extensions&lt;/code&gt;, &lt;code&gt;typing-extensions&lt;/code&gt;, &lt;code&gt;pre-commit&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Source uniquement&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;MIT&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;igoristan&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;SUT public, application web volontairement chaotique&lt;/td&gt;&#xA;          &lt;td&gt;TypeScript&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;&lt;strong&gt;6.0.x&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;React 19.2&lt;/strong&gt;, &lt;strong&gt;Vike 0.4.258&lt;/strong&gt; (SSG), &lt;strong&gt;Vite 7.3&lt;/strong&gt;, &lt;strong&gt;TailwindCSS 4&lt;/strong&gt; (alpha), &lt;code&gt;valibot&lt;/code&gt;, &lt;code&gt;react-hook-form&lt;/code&gt;, &lt;code&gt;react-dropzone&lt;/code&gt;, &lt;code&gt;usehooks-ts&lt;/code&gt;, &lt;code&gt;throttleit&lt;/code&gt;, &lt;code&gt;lucide-react&lt;/code&gt;, &lt;code&gt;uuid&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;pnpm 11&lt;/code&gt;, &lt;strong&gt;Node 24&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;wireit&lt;/code&gt;&lt;/strong&gt; (orchestrateur), &lt;code&gt;eslint 9&lt;/code&gt; (flat), &lt;code&gt;prettier 3.5&lt;/code&gt;, &lt;code&gt;husky&lt;/code&gt;, &lt;code&gt;lint-staged&lt;/code&gt;, &lt;code&gt;commitlint&lt;/code&gt;, &lt;code&gt;commitizen&lt;/code&gt;, &lt;code&gt;terser&lt;/code&gt;, &lt;code&gt;rollup-plugin-visualizer&lt;/code&gt;, &lt;code&gt;vite-plugin-compression&lt;/code&gt;, &lt;code&gt;@qalisa/vike-plugin-sitemap&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;GitHub Pages (&lt;code&gt;/igoristan/&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;&lt;em&gt;aucune&lt;/em&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;tests-workers&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Backend OTP / Corsicadex pour exercer la &lt;strong&gt;parallélisation&lt;/strong&gt; et les &lt;strong&gt;tests avec appels API&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;TypeScript&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;&lt;strong&gt;5.9.3&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Vercel Edge Functions&lt;/strong&gt; (&lt;code&gt;runtime: &amp;quot;edge&amp;quot;&lt;/code&gt;), &lt;code&gt;@upstash/redis&lt;/code&gt;, &lt;code&gt;otplib&lt;/code&gt;, types &lt;code&gt;next&lt;/code&gt; (&lt;code&gt;NextRequest&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;pnpm 11.x&lt;/code&gt;, &lt;code&gt;@vercel/node&lt;/code&gt; (types)&lt;/td&gt;&#xA;          &lt;td&gt;Vercel : &lt;code&gt;https://tests-workers.vercel.app&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;ISC&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ocarina-holy-book&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Documentation publique + skills IA + PDF&lt;/td&gt;&#xA;          &lt;td&gt;TypeScript&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;&lt;strong&gt;6.0.x&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;VitePress 2 alpha&lt;/strong&gt;, theme &lt;code&gt;@sugarat/theme&lt;/code&gt;, &lt;code&gt;vue 3.5&lt;/code&gt;, &lt;code&gt;pagefind&lt;/code&gt;, &lt;code&gt;vitepress-plugin-image-optimize&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;pnpm 11&lt;/code&gt;, Node 24, &lt;code&gt;eslint 9&lt;/code&gt;, &lt;code&gt;prettier 3.8&lt;/code&gt;, &lt;code&gt;husky&lt;/code&gt;, &lt;code&gt;lint-staged&lt;/code&gt;, &lt;code&gt;commitlint&lt;/code&gt;, &lt;code&gt;sass-embedded&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;GitHub Pages (&lt;code&gt;/ocarina-holy-book/&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;MIT&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;python-early-adopter&#34;&gt;Python early adopter&lt;a class=&#34;anchor&#34; href=&#34;#python-early-adopter&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Apport Python 3.12+&lt;/th&gt;&#xA;          &lt;th&gt;Utilisation dans Ocarina&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;PEP 695&lt;/strong&gt; (&lt;code&gt;class Foo[T]:&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;Signature générique partout (&lt;code&gt;TestSuite[Driver]&lt;/code&gt;, &lt;code&gt;Watcher[Driver]&lt;/code&gt;, &lt;code&gt;Test[Driver]&lt;/code&gt;, &lt;code&gt;Result[T]&lt;/code&gt;, &lt;code&gt;Ok[T]&lt;/code&gt;, &lt;code&gt;Thunk[T]&lt;/code&gt;, etc.)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;PEP 695 type aliases&lt;/strong&gt; (&lt;code&gt;type X = ...&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;Tous les alias : &lt;code&gt;type Result[T] = Ok[T] | Fail&lt;/code&gt;, &lt;code&gt;type Effect = Callable[[], None]&lt;/code&gt;, &lt;code&gt;type Thunk[T] = Callable[[], T]&lt;/code&gt;, &lt;code&gt;type Mode = Literal[...]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;TypeGuard&lt;/code&gt;&lt;/strong&gt; (Python 3.10+)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_ok&lt;/code&gt;, &lt;code&gt;is_fail&lt;/code&gt;, &lt;code&gt;is_test_result_ok&lt;/code&gt;, &lt;code&gt;is_test_result_fail&lt;/code&gt;, &lt;code&gt;is_test_result_skipped&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;Self&lt;/code&gt;&lt;/strong&gt; (3.11+)&lt;/td&gt;&#xA;          &lt;td&gt;Retours fluides des POMs, des loggers, des invariants&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;@final&lt;/code&gt;&lt;/strong&gt; (3.8+)&lt;/td&gt;&#xA;          &lt;td&gt;Verrouille les classes critiques (&lt;code&gt;Watcher&lt;/code&gt;, &lt;code&gt;Test&lt;/code&gt;, &lt;code&gt;TestCycle&lt;/code&gt;, &lt;code&gt;ChainRunner&lt;/code&gt;, &lt;code&gt;Ok&lt;/code&gt;, &lt;code&gt;Fail&lt;/code&gt;, …)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;Unpack&lt;/code&gt;&lt;/strong&gt; (3.11+)&lt;/td&gt;&#xA;          &lt;td&gt;Signature de &lt;code&gt;HumanizedDriver&lt;/code&gt; côté &lt;code&gt;ocarina-example&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;Protocol&lt;/code&gt;&lt;/strong&gt; (3.8+)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ScreenshotDriver&lt;/code&gt;, &lt;code&gt;SupportsWrite[str]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;Literal&lt;/code&gt;&lt;/strong&gt; (3.8+)&lt;/td&gt;&#xA;          &lt;td&gt;Toutes les énumérations (&lt;code&gt;SeleniumCliStoreKeys&lt;/code&gt;, &lt;code&gt;LOGGERS_CHOICES&lt;/code&gt;, &lt;code&gt;Mode&lt;/code&gt;)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;Never&lt;/code&gt;&lt;/strong&gt; (3.11+)&lt;/td&gt;&#xA;          &lt;td&gt;Signature de &lt;code&gt;_SilentArgumentParser.error&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;&lt;strong&gt;Conséquence pratique&lt;/strong&gt; : il existe un workflow CI dédié &lt;code&gt;unstable_python_full_build.yml&lt;/code&gt; qui pousse Python &lt;strong&gt;3.15-dev&lt;/strong&gt; mensuellement (&lt;code&gt;cron: &amp;quot;0 3 1 * *&amp;quot;&lt;/code&gt;), parce qu&amp;rsquo;Ocarina veut être prêt pour le prochain interpréteur dès qu&amp;rsquo;il sort. Voir &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/02-ocarina-workflows/&#34;&gt;&lt;code&gt;../10-cicd/02-ocarina-workflows.md&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>01.02 — ISTQB vs pytest / Jest / Mocha</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/01-philosophy/02-istqb-vs-pytest/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/01-philosophy/02-istqb-vs-pytest/</guid>
      <description>&lt;h1 id=&#34;0102istqb-vs-pytest-jest-mocha&#34;&gt;01.02 — ISTQB vs pytest / Jest / Mocha&lt;a class=&#34;anchor&#34; href=&#34;#0102istqb-vs-pytest-jest-mocha&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;h2 id=&#34;le-constat-méthodologique&#34;&gt;Le constat méthodologique&lt;a class=&#34;anchor&#34; href=&#34;#le-constat-m%c3%a9thodologique&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Au-delà du pari technique sur la barrière techniques / non-techniques (voir &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/01-philosophy/01-flip-the-problem/&#34;&gt;&lt;code&gt;01-flip-the-problem.md&lt;/code&gt;&lt;/a&gt;), il y a un &lt;strong&gt;second pari&lt;/strong&gt; : celui du vocabulaire métier.&lt;/p&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;L&amp;rsquo;ISTQB et les testeurs professionnels ont construit, depuis des décennies, un vocabulaire précis et éprouvé : &lt;em&gt;cycles de test&lt;/em&gt;, &lt;em&gt;campagnes&lt;/em&gt;, &lt;em&gt;suites de test&lt;/em&gt;, &lt;em&gt;cas de test&lt;/em&gt;, &lt;em&gt;pas de test&lt;/em&gt;. Une hiérarchie claire, pensée pour &lt;strong&gt;organiser, tracer et piloter&lt;/strong&gt; la qualité logicielle.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.02 — Arborescence du module ocarina</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/02-module-tree/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/02-module-tree/</guid>
      <description>&lt;h1 id=&#34;0202arborescence-du-module-ocarina&#34;&gt;02.02 — Arborescence du module &lt;code&gt;ocarina&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0202arborescence-du-module-ocarina&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;p&gt;Le module Python source contient &lt;strong&gt;134 fichiers &lt;code&gt;.py&lt;/code&gt;&lt;/strong&gt; répartis en quatre couches conceptuelles.&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;src/ocarina/&#xA;├── __init__.py                              # vide — pas de &amp;#34;facade&amp;#34; globale, on importe précisément&#xA;├── py.typed                                 # PEP 561 : déclare le package comme &amp;#34;typed&amp;#34;&#xA;│&#xA;├── railway/                                 # Couche 1 — primitives FP&#xA;│   ├── __init__.py&#xA;│   └── result.py                            # Ok[T] / Fail / Result[T] / is_ok / is_fail&#xA;│&#xA;├── custom_types/                            # Couche 2 — types et alias&#xA;│   ├── __init__.py&#xA;│   ├── effect.py                            # type Effect = Callable[[], None]&#xA;│   ├── thunk.py                             # type Thunk[T] = Callable[[], T]&#xA;│   ├── tpom.py                              # TypeVar TPOM bound POMBase&#xA;│   ├── supports_write.py                    # Protocol SupportsWrite[T], reflète type non exposé par lib standard Python&#xA;│   ├── built_web_driver.py                  # type BuiltWebDriver[Driver] = tuple[Driver, Effect]&#xA;│   ├── scenario.py                          # Scenario[Driver] (frozen dataclass)&#xA;│   ├── test_components.py                   # TestChain / TestSetup / TestTeardown / TestWatchers&#xA;│   ├── test_runner.py                       # TestRunner[Driver]&#xA;│   ├── oc_test.py                           # TestName / TestScenario / TestScenarioFragment&#xA;│   ├── oc_test_layers.py                    # TestResult / TestSuiteResult / TestCampaignResults / TestCycleResults&#xA;│   ├── selenium/&#xA;│   │   ├── built_web_driver.py              # BuiltSeleniumWebDriver&#xA;│   │   ├── oc_test_scenario.py&#xA;│   │   ├── supported_browsers.py            # type SupportedSeleniumBrowser = Literal[&amp;#34;chrome&amp;#34;, &amp;#34;firefox&amp;#34;, &amp;#34;edge&amp;#34;, &amp;#34;safari&amp;#34;]&#xA;│   │   └── web_drivers_pool.py              # type SeleniumWebDriversPool = WebDriversPool[WebDriver]&#xA;│   └── playwright/&#xA;│       ├── built_web_driver.py              # type BuiltPlaywrightDriver = BuiltWebDriver[PlaywrightDriver]&#xA;│       ├── oc_test_scenario.py              # PlaywrightTestScenario / PlaywrightTestScenarioFragment&#xA;│       ├── supported_browsers.py            # type SupportedPlaywrightBrowser = Literal[&amp;#34;chromium&amp;#34;, &amp;#34;firefox&amp;#34;, &amp;#34;webkit&amp;#34;]&#xA;│       └── web_drivers_pool.py              # type PlaywrightDriversPool = WebDriversPool[PlaywrightDriver]&#xA;│&#xA;├── custom_errors/                           # Couche 2 : erreurs&#xA;│   ├── __init__.py&#xA;│   └── test_framework/&#xA;│       ├── __init__.py&#xA;│       ├── no_matching_branch.py            # NoMatchingBranchError&#xA;│       ├── pages.py                         # PageVerificationError&#xA;│       ├── driver_died.py                   # DriverDiedError&#xA;│       └── campaigns.py&#xA;│&#xA;├── custom_invariants/                       # Couche 2 : invariants prêts-à-l&amp;#39;emploi&#xA;│   ├── __init__.py&#xA;│   └── testing/&#xA;│       ├── __init__.py&#xA;│       ├── workers.py                       # validate_workers_amount&#xA;│       ├── oc_test_runners_ids.py           # validate_test_runners_ids&#xA;│       ├── oc_test_runners_names.py         # validate_test_runners_names (unique + valid filename)&#xA;│       ├── oc_test_suites_names.py&#xA;│       ├── oc_test_campaigns_names.py&#xA;│       └── oc_test_cycles_names.py&#xA;│&#xA;├── ports/                                   # Couche 3 : ports (interfaces)&#xA;│   ├── __init__.py&#xA;│   ├── ilogger.py                           # ILogger (ABC)&#xA;│   └── itake_screenshot.py                  # ITakeScreenshot[Driver] (Protocol)&#xA;│&#xA;├── pom/                                     # Couche 3 : Page Object Model&#xA;│   ├── __init__.py&#xA;│   ├── base.py                              # POMBase (ABC : verify + get_current_title)&#xA;│   ├── selenium/&#xA;│   │   ├── __init__.py&#xA;│   │   └── muted.py                         # MutedPOM utilitaire&#xA;│   └── playwright/&#xA;│       ├── __init__.py&#xA;│       └── muted.py                         # MutedPlaywrightPOM utilitaire&#xA;│&#xA;├── aggregates/                              # Couche 3 : agrégats de résultats&#xA;│   ├── __init__.py&#xA;│   └── tests_layers.py                      # is_test_result_ok / _fail / _skipped (TypeGuard)&#xA;│&#xA;├── dsl/                                     # Couche 4 : DSL Ocarina&#xA;│   ├── __init__.py&#xA;│   ├── invariants/&#xA;│   │   ├── __init__.py&#xA;│   │   ├── validate.py                      # entry-point validate(), BusinessInvariantValidator, FrameworkInvariantValidator&#xA;│   │   ├── assertions.py                    # is_str / is_email / is_positive / is_in / has_unique_elements / each / ...&#xA;│   │   ├── errors.py                        # InvariantViolationError / DuplicatesError / AggregateInvariantViolationError&#xA;│   │   └── internals/&#xA;│   │       ├── __init__.py&#xA;│   │       └── validation_chain.py          # ValidationStartBlock / ValidationAssertBlock / _ValidationChain / _any_of / chain_validations&#xA;│   ├── testing/&#xA;│   │   ├── __init__.py&#xA;│   │   ├── oc_test.py                       # class Test[Driver]&#xA;│   │   ├── oc_test_suite.py                 # class TestSuite[Driver]&#xA;│   │   ├── oc_test_campaign.py              # class TestCampaign[Driver] + campaign_has_failed&#xA;│   │   ├── oc_test_cycle.py                 # class TestCycle[Driver] + type Mode + has_test_cycle_failed&#xA;│   │   ├── filter_tests_by_ids.py&#xA;│   │   ├── watcher.py                       # class Watcher[Driver]&#xA;│   │   ├── internals/&#xA;│   │   │   ├── __init__.py&#xA;│   │   │   ├── test_executor.py             # class TestExecutor[Driver] + ExecutionOutcome (frozen, slots)&#xA;│   │   │   └── test_flow.py                 # class TestFlow[Driver]&#xA;│   │   ├── selenium/&#xA;│   │   │   ├── __init__.py&#xA;│   │   │   ├── create_test.py               # create_selenium_test(...)&#xA;│   │   │   └── create_watcher.py            # create_selenium_watcher(...) + type SeleniumWatcher&#xA;│   │   └── playwright/&#xA;│   │       ├── __init__.py&#xA;│   │       ├── create_test.py               # create_playwright_test(...)&#xA;│   │       └── create_watcher.py            # create_playwright_watcher(...) + type PlaywrightWatcher&#xA;│   └── testing_with_railway/&#xA;│       ├── __init__.py&#xA;│       ├── chain_actions.py                 # class ChainRunner[T] + chain_actions&#xA;│       ├── match_page.py                    # When + _match_page_builder + create_match_page&#xA;│       ├── constructors/&#xA;│       │   ├── __init__.py&#xA;│       │   └── create_act.py                # primitive de bas niveau (à wrapper côté projet)&#xA;│       └── internals/&#xA;│           ├── __init__.py&#xA;│           └── action_chain.py              # ActionStart → ActionFailure → ActionSuccess → ActionChain&#xA;│                                            # + Neutral{Start,Failure,Success}  (rail d&amp;#39;échec)&#xA;│&#xA;├── infra/                                   # Couche 5 : infrastructure&#xA;│   ├── __init__.py&#xA;│   ├── drivers_pool.py                      # class WebDriversPool[Driver] + WarmupTimeoutError&#xA;│   ├── driver_builder.py                    # class DriverBuilder[Driver]&#xA;│   ├── screenshotter.py                     # class Screenshotter[TDriver] + ScreenshotterConfig&#xA;│   ├── act_counter.py                       # class ActCounter (interface)&#xA;│   ├── selenium/&#xA;│   │   ├── __init__.py&#xA;│   │   ├── create_driver.py                 # _build_firefox / _build_chrome / _build_edge / _build_safari&#xA;│   │   ├── create_drivers_pool.py           # create_selenium_drivers_pool&#xA;│   │   ├── create_screenshotter.py          # create_selenium_screenshotter&#xA;│   │   ├── driver_healthcheck.py            # driver_healthcheck (ping driver.title)&#xA;│   │   └── mixins.py                        # SeleniumTitleMixin (détrompeur de typage)&#xA;│   └── playwright/&#xA;│       ├── __init__.py&#xA;│       ├── create_driver.py                 # create_playwright_driver (chromium / firefox / webkit)&#xA;│       ├── create_drivers_pool.py           # create_playwright_drivers_pool&#xA;│       ├── create_screenshotter.py          # create_playwright_screenshotter + _playwright_save_full_page&#xA;│       ├── driver.py                        # class PlaywrightDriver (wrapper sync)&#xA;│       ├── driver_healthcheck.py            # playwright_driver_healthcheck (ping driver.title)&#xA;│       └── mixins.py                        # PlaywrightTitleMixin (détrompeur de typage)&#xA;│&#xA;└── opinionated/                             # Couche 6 : opt-in, tout ce qui est &amp;#34;joli mais remplaçable&amp;#34;&#xA;    ├── __init__.py&#xA;    ├── consts/loggers_choices.py            # LOGGERS_CHOICES = (&amp;#34;terminal&amp;#34;, &amp;#34;file&amp;#34;, &amp;#34;terminal+file&amp;#34;, &amp;#34;muted&amp;#34;)&#xA;    ├── infra/&#xA;    │   ├── __init__.py&#xA;    │   └── act_counter.py                   # ThreadsBasedActCounter (compteur thread-local)&#xA;    ├── cli/&#xA;    │   ├── __init__.py&#xA;    │   ├── builder.py                       # CliBuilder + CliArg + _SilentArgumentParser&#xA;    │   ├── store.py                         # CliStore[TKeys] + _CliField[T] + field(...)&#xA;    │   ├── phantoms.py                      # phantom_validate (no-op predicate)&#xA;    │   ├── selenium/&#xA;    │   │   ├── __init__.py&#xA;    │   │   ├── cli_store_singleton.py       # SeleniumCliStoreSingleton (push / get)&#xA;    │   │   └── create_cli_store.py          # create_selenium_{auto,win,macos,linux}_cli_store&#xA;    │   └── playwright/&#xA;    │       ├── __init__.py&#xA;    │       ├── cli_store_singleton.py       # PlaywrightCliStoreSingleton (push / get)&#xA;    │       └── create_cli_store.py          # create_playwright_{,auto_}cli_store&#xA;    ├── dsl/&#xA;    │   ├── __init__.py&#xA;    │   └── drive_page.py                    # drive_page = chain_actions, alias sémantique&#xA;    ├── launcher/&#xA;    │   ├── __init__.py&#xA;    │   └── bootstrap.py                     # bootstrap(...) + run_plugins(*plugins, exceptions_logger)&#xA;    ├── loggers/&#xA;    │   ├── __init__.py&#xA;    │   ├── create_matching_logger.py        # create_matching_logger(&amp;#34;terminal&amp;#34;|&amp;#34;file&amp;#34;|...) + get_default_log_dir&#xA;    │   ├── print_logger.py                  # PrintLogger (ANSI)&#xA;    │   ├── file_logger.py                   # FileLogger (taxonomie -&amp;gt; arbre de fichiers)&#xA;    │   ├── print_and_file_logger.py&#xA;    │   ├── muted_logger.py&#xA;    │   ├── custom_types/supported_loggers.py&#xA;    │   └── utils/&#xA;    │       ├── __init__.py&#xA;    │       └── format_metadata_str.py       # format_utc_date_metadata_str / format_current_thread_metadata_str / concat_metadata&#xA;    └── plugins/&#xA;        ├── __init__.py&#xA;        └── reports/&#xA;            ├── __init__.py&#xA;            ├── pretty_print_results.py      # rapport ANSI hiérarchique&#xA;            ├── results_to_json.py           # générateur JSON&#xA;            ├── docx_tests_proofs.py         # générateur DOCX (consomme l&amp;#39;arbre de logs)&#xA;            └── timing.py                    # context manager `with timing(prefix=&amp;#34;Duration:&amp;#34;)`&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;schéma-en-couches&#34;&gt;Schéma en couches&lt;a class=&#34;anchor&#34; href=&#34;#sch%c3%a9ma-en-couches&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;┌──────────────────────────────────────────────────────────────────────────┐&#xA;│ USER PROJECT (e.g. ocarina-example, ocarina-with-ai-example)             │&#xA;│  ┌────────────────────┐  ┌────────────────────┐  ┌─────────────────────┐ │&#xA;│  │ pages/ (POMBase)   │  │ scenarios/ (Test)  │  │ adapters/ (act, ...)│ │&#xA;│  └────────────────────┘  └────────────────────┘  └─────────────────────┘ │&#xA;└─────────────────────────────────┬────────────────────────────────────────┘&#xA;                                  │ imports&#xA;┌─────────────────────────────────▼────────────────────────────────────────┐&#xA;│ COUCHE 6 — opinionated/  (CLI, loggers, plugins, bootstrap)              │&#xA;│   opt-in. Peut être remplacée intégralement.                             │&#xA;└─────────────────────────────────┬────────────────────────────────────────┘&#xA;                                  │&#xA;┌─────────────────────────────────▼────────────────────────────────────────┐&#xA;│ COUCHE 5 — infra/  (pool, builder, screenshotter, adapters Selenium)     │&#xA;│   I/O. Pas de logique DSL.                                               │&#xA;└─────────────────────────────────┬────────────────────────────────────────┘&#xA;                                  │&#xA;┌─────────────────────────────────▼────────────────────────────────────────┐&#xA;│ COUCHE 4 — dsl/  (testing, invariants, testing_with_railway)             │&#xA;│   DSL pur, sans I/O. Toute la grammaire.                                 │&#xA;└─────────────────────────────────┬────────────────────────────────────────┘&#xA;                                  │&#xA;┌─────────────────────────────────▼────────────────────────────────────────┐&#xA;│ COUCHE 3 — ports/  +  pom/  +  aggregates/                               │&#xA;│   Interfaces et abstractions.                                            │&#xA;└─────────────────────────────────┬────────────────────────────────────────┘&#xA;                                  │&#xA;┌─────────────────────────────────▼────────────────────────────────────────┐&#xA;│ COUCHE 2 — custom_types/  +  custom_errors/  +  custom_invariants/       │&#xA;│   Shapes, alias, types, exceptions, invariants prêts-à-l&amp;#39;emploi.       │&#xA;└─────────────────────────────────┬────────────────────────────────────────┘&#xA;                                  │&#xA;┌─────────────────────────────────▼────────────────────────────────────────┐&#xA;│ COUCHE 1 — railway/  (Result, Ok, Fail, is_ok, is_fail)                  │&#xA;│   La primitive fonctionnelle. Aucune dépendance.                         │&#xA;└──────────────────────────────────────────────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Cette &lt;strong&gt;stratification stricte&lt;/strong&gt; garantit l&amp;rsquo;absence de &lt;em&gt;cycles d&amp;rsquo;import&lt;/em&gt; et permet de remplacer chaque couche supérieure sans toucher aux couches inférieures :&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.03.02 — La machine à états du builder</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/02-action-chain-states/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/02-action-chain-states/</guid>
      <description>&lt;h1 id=&#34;020302la-machine-à-états-du-builder&#34;&gt;02.03.02 — La machine à états du builder&lt;a class=&#34;anchor&#34; href=&#34;#020302la-machine-%c3%a0-%c3%a9tats-du-builder&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing_with_railway/internals/action_chain.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/testing_with_railway/internals/action_chain.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;C&amp;rsquo;est ici qu&amp;rsquo;est implémentée &lt;em&gt;toute&lt;/em&gt; la mécanique ROP : le passage par quatre états successifs typés, qui interdit littéralement (au sens du &lt;em&gt;type-checker&lt;/em&gt;) toute fantaisie syntaxique. Le DSL est aussi son propre système immunitaire.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;vue-densemble&#34;&gt;Vue d&amp;rsquo;ensemble&lt;a class=&#34;anchor&#34; href=&#34;#vue-densemble&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;                      ┌─────────────────┐&#xA;   start(action)  →   │  ActionStart[T] │     un Action[T] = Thunk[Result[T]]&#xA;                      └────────┬────────┘&#xA;                               │ .failure(failure_handler)&#xA;                               ▼&#xA;                      ┌─────────────────┐&#xA;                      │ ActionFailure[T]│&#xA;                      └────────┬────────┘&#xA;                               │ .success(success_handler)&#xA;                               ▼&#xA;                      ┌─────────────────┐&#xA;                      │ ActionSuccess[T]│&#xA;                      └────────┬────────┘&#xA;                               │ .execute()   ── exécute action(), fire le handler&#xA;                               ▼&#xA;                      ┌─────────────────┐&#xA;                      │ ActionChain[T]  │     contient (has_failed, result)&#xA;                      └──────┬──────────┘&#xA;                             │ .then(next_action_or_start)&#xA;              ┌──────────────┴──────────────┐&#xA;              │                             │&#xA;   has_failed=True                   has_failed=False&#xA;              │                             │&#xA;              ▼                             ▼&#xA;   ┌──────────────────┐         ┌──────────────────┐&#xA;   │ NeutralAction-   │         │ ActionStart[T]   │  → cycle recommence&#xA;   │ Start[T]         │         │  (l&amp;#39;action suiv. │&#xA;   │ (rail d&amp;#39;échec :  │         │   sera exécutée) │&#xA;   │  toute la chaîne │         └──────────────────┘&#xA;   │  devient no-op,  │&#xA;   │ tout en gardant  │&#xA;   │ l&amp;#39;API fluide)    │&#xA;   └──────────────────┘&#xA;              │&#xA;              ▼ (.failure → .success → .execute, tout en no-op)&#xA;   ┌──────────────────┐&#xA;   │  ActionChain[T]  │   has_failed=True, result=&amp;lt;le Fail accumulé&amp;gt;&#xA;   └──────────────────┘&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;les-types-action-failurehandler-succhandler&#34;&gt;Les types &lt;code&gt;Action&lt;/code&gt;, &lt;code&gt;FailureHandler&lt;/code&gt;, &lt;code&gt;SuccHandler&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#les-types-action-failurehandler-succhandler&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; Action[T]        &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Thunk[Result[T]]      &lt;span style=&#34;color:#6272a4&#34;&gt;# () -&amp;gt; Result[T]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; FailureHandler   &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Callable[[Exception], &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; SuccHandler      &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Effect                &lt;span style=&#34;color:#6272a4&#34;&gt;# () -&amp;gt; None&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;Action[T]&lt;/code&gt; est un &lt;code&gt;Thunk&lt;/code&gt;&lt;/strong&gt;. Cela veut dire qu&amp;rsquo;au moment où &lt;code&gt;ActionStart(action)&lt;/code&gt; est appelé, &lt;strong&gt;rien n&amp;rsquo;est exécuté&lt;/strong&gt;. L&amp;rsquo;action est juste capturée.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;FailureHandler&lt;/code&gt; reçoit l&amp;rsquo;exception&lt;/strong&gt;, mais ne reçoit &lt;strong&gt;pas&lt;/strong&gt; le &lt;code&gt;Fail&lt;/code&gt; ni le &lt;code&gt;Result&lt;/code&gt;. C&amp;rsquo;est volontaire : 99% des handlers veulent tracer l&amp;rsquo;exception, prendre un screenshot, et c&amp;rsquo;est tout.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;SuccHandler&lt;/code&gt; est un &lt;code&gt;Effect&lt;/code&gt;&lt;/strong&gt; (sans argument). Idem : un handler de succès log un message statique, fait un screenshot, pas besoin du résultat.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;typestate-pattern-pourquoi-on-ne-peut-pas-se-tromper&#34;&gt;Typestate Pattern : pourquoi on ne peut pas se tromper&lt;a class=&#34;anchor&#34; href=&#34;#typestate-pattern-pourquoi-on-ne-peut-pas-se-tromper&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;L&amp;rsquo;enchaînement est &lt;strong&gt;strictement linéaire&lt;/strong&gt;. Chaque méthode renvoie un type différent, qui n&amp;rsquo;expose que la &lt;em&gt;suite logique&lt;/em&gt; de l&amp;rsquo;enchaînement :&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.04.02 — Catalogue des assertions builtin</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/04-invariants/02-assertions/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/04-invariants/02-assertions/</guid>
      <description>&lt;h1 id=&#34;020402catalogue-des-assertions-builtin&#34;&gt;02.04.02 — Catalogue des assertions builtin&lt;a class=&#34;anchor&#34; href=&#34;#020402catalogue-des-assertions-builtin&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/invariants/assertions.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/invariants/assertions.py&lt;/code&gt;&lt;/a&gt; ~25 prédicats.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;p&gt;Toutes les assertions suivent le &lt;strong&gt;même contrat&lt;/strong&gt; :&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Soit un prédicat direct &lt;code&gt;(value: T) -&amp;gt; None&lt;/code&gt; qui lève &lt;code&gt;InvariantViolationError&lt;/code&gt; si le contrat n&amp;rsquo;est pas respecté.&lt;/li&gt;&#xA;&lt;li&gt;Soit une &lt;strong&gt;closure&lt;/strong&gt; quand il faut passer un argument de configuration (&lt;code&gt;is_equal_to(cmp)&lt;/code&gt;, etc.).&lt;/li&gt;&#xA;&lt;li&gt;Soit, exceptionnellement, une &lt;strong&gt;higher-order function (HOF)&lt;/strong&gt; comme &lt;code&gt;each&lt;/code&gt;, placée ici par pragmatisme (créer un fichier entier pour ce seul cas serait &lt;em&gt;overkill&lt;/em&gt;).&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;tableau-complet&#34;&gt;Tableau complet&lt;a class=&#34;anchor&#34; href=&#34;#tableau-complet&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Assertion&lt;/th&gt;&#xA;          &lt;th&gt;Direct / closure / HOF&lt;/th&gt;&#xA;          &lt;th&gt;Domaine&lt;/th&gt;&#xA;          &lt;th&gt;Description&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_str(value)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;direct&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Any&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Lève si &lt;code&gt;value&lt;/code&gt; n&amp;rsquo;est pas une &lt;code&gt;str&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_none(value)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;direct&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Any&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Lève si &lt;code&gt;value is not None&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_not_none(value)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;direct&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Any&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Lève si &lt;code&gt;value is None&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_equal_to(cmp)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;closure&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Any&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Retourne &lt;code&gt;(value) -&amp;gt; None&lt;/code&gt; qui lève si &lt;code&gt;value != cmp&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_not_equal_to(cmp)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;closure&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Any&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Retourne &lt;code&gt;(value) -&amp;gt; None&lt;/code&gt; qui lève si &lt;code&gt;value == cmp&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_less_than(cmp)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;closure&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;value &amp;lt; cmp&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_less_than_or_equal_to(cmp)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;closure&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;value &amp;lt;= cmp&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_greater_than(cmp)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;closure&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;value &amp;gt; cmp&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_greater_than_or_equal_to(cmp)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;closure&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;value &amp;gt;= cmp&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_positive(value)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;direct&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;value &amp;gt;= 0&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_not_zero(value)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;direct&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;value != 0&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_in(elements)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;closure&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Any&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;value in tuple(elements)&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_file(value)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;direct&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;str | Path&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Path(value).is_file()&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_dir(value)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;direct&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;str | Path&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Path(value).is_dir()&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_iso_date_string(value)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;direct&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;datetime.fromisoformat(value)&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_iso_utc_date_string(value)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;direct&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Vérifie ISO + &lt;code&gt;tzinfo == UTC&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_email(value)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;direct&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Pas d&amp;rsquo;espace, exactement 1 &lt;code&gt;@&lt;/code&gt;, parts non vides, domaine contient &lt;code&gt;.&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;has_unique_elements(*, key=None)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;closure&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Iterable[Any]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Détecte les doublons (lève &lt;code&gt;DuplicatesError&lt;/code&gt;). Type-strict (&lt;code&gt;1 ≠ True&lt;/code&gt;), supporte les unhashables.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_empty(value)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;direct&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Sized&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;len(value) == 0&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_truthy(value)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;direct&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Any&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;bool(value) is True&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_valid_filename(value)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;direct&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Cross-platform : chars interdits, mots réservés Windows, pas leading/trailing space ni dot, longueur ≤ 255&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;each(predicate)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;HOF&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Iterable[Any]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Applique &lt;code&gt;predicate&lt;/code&gt; à chaque élément&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;quelques-implémentations-en-détail&#34;&gt;Quelques implémentations en détail&lt;a class=&#34;anchor&#34; href=&#34;#quelques-impl%c3%a9mentations-en-d%c3%a9tail&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;is_email&#34;&gt;&lt;code&gt;is_email&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#is_email&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;is_email&lt;/span&gt;(value: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&amp;#34;Assert that the string is a valid email address (fast check).&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34; &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; value:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;raise&lt;/span&gt; InvariantViolationError(&lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;value&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39; must not contain whitespace.&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; value&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;count(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;@&amp;#34;&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;raise&lt;/span&gt; InvariantViolationError(&lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;value&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39; must contain exactly one &amp;#39;@&amp;#39; character.&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    local_part, domain_part &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; value&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;split(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;@&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;not&lt;/span&gt; local_part &lt;span style=&#34;color:#ff79c6&#34;&gt;or&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;not&lt;/span&gt; domain_part:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;raise&lt;/span&gt; InvariantViolationError(&lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;value&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39; must have non-empty local and domain parts.&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;.&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; domain_part:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;raise&lt;/span&gt; InvariantViolationError(&lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Domain part of &amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;value&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39; must contain at least one &amp;#39;.&amp;#39;.&amp;#34;&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Quatre vérifications minimales. Pas de regex RFC5322. C&amp;rsquo;est volontaire : l&amp;rsquo;auteur fait le pari que la validation &lt;em&gt;exhaustive&lt;/em&gt; d&amp;rsquo;un email se fait en envoyant un email, pas en regex.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.05.02 — TestExecutor[Driver]</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/05-orchestration/02-test-executor/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/05-orchestration/02-test-executor/</guid>
      <description>&lt;h1 id=&#34;020502testexecutordriver&#34;&gt;02.05.02 — &lt;code&gt;TestExecutor[Driver]&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#020502testexecutordriver&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing/internals/test_executor.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/testing/internals/test_executor.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;&lt;strong&gt;Responsabilité unique&lt;/strong&gt; : exécuter &lt;strong&gt;une seule tentative&lt;/strong&gt; d&amp;rsquo;un test, avec &lt;strong&gt;un seul driver&lt;/strong&gt;. Ne connaît ni le rejeu, ni la pool de drivers, ni l&amp;rsquo;agrégation au niveau suite.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;executionoutcome&#34;&gt;&lt;code&gt;ExecutionOutcome&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#executionoutcome&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@final&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@dataclass(frozen&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;True&lt;/span&gt;, slots&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;True&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;ExecutionOutcome&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    result: TestResult&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    skipped: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;bool&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    setup_failed: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;bool&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    should_retry: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;bool&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    steps_count: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Champ&lt;/th&gt;&#xA;          &lt;th&gt;Type&lt;/th&gt;&#xA;          &lt;th&gt;Sens&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;result&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestResult&lt;/code&gt; = &lt;code&gt;Result[Any] | None&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Le résultat de la chaîne (Ok, Fail), ou &lt;code&gt;None&lt;/code&gt; si skip / setup_failed.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;skipped&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;True si &lt;code&gt;test_runner.skipped&lt;/code&gt; (i.e. &lt;code&gt;Test(skipped=True)&lt;/code&gt;).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;setup_failed&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;True si la fonction &lt;code&gt;setup()&lt;/code&gt; du scénario a levé.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;should_retry&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;True si la &lt;em&gt;règle de rejeu&lt;/em&gt; s&amp;rsquo;applique (transient_error détecté).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;steps_count&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Nombre d&amp;rsquo;&lt;code&gt;act&lt;/code&gt; appelés ; &lt;code&gt;-1&lt;/code&gt; si skip ou setup_failed.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;slots=True&lt;/code&gt;&lt;/strong&gt; : empêche l&amp;rsquo;ajout dynamique d&amp;rsquo;attributs et économise mémoire. C&amp;rsquo;est un objet qui circule en hot-path, le &lt;code&gt;slots&lt;/code&gt; est justifié.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;frozen=True&lt;/code&gt;&lt;/strong&gt; : immutable, sûr à partager entre threads.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;@final&lt;/code&gt;&lt;/strong&gt; : pas d&amp;rsquo;héritage.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;ordre-dexécution-dune-tentative&#34;&gt;Ordre d&amp;rsquo;exécution d&amp;rsquo;une tentative&lt;a class=&#34;anchor&#34; href=&#34;#ordre-dex%c3%a9cution-dune-tentative&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;┌──────────────────────────────────────────────────────────────────────┐&#xA;│  test_runner = test.spawn(driver, logger_with_taxonomy)              │&#xA;└──────────────────────────────┬───────────────────────────────────────┘&#xA;                               │&#xA;                               ▼&#xA;              ┌─────────────────────────────────┐&#xA;              │  test_runner.skipped ?          │── True ─► return Outcome(skipped=True, ...)&#xA;              └────────────────┬────────────────┘&#xA;                               │ False&#xA;                               ▼&#xA;              ┌─────────────────────────────────┐&#xA;              │  logger.test_name(test.name)    │   (annotation)&#xA;              └────────────────┬────────────────┘&#xA;                               │&#xA;                               ▼&#xA;              ┌─────────────────────────────────┐&#xA;              │  setup() (si non-None)          │── leve ─► teardown() (si non-None)&#xA;              └────────────────┬────────────────┘            ↓&#xA;                               │                  return Outcome(setup_failed=True, should_retry=True, ...)&#xA;                               ▼&#xA;              ┌─────────────────────────────────┐&#xA;              │  watchers.start(driver, logger, │&#xA;              │                 take_screenshot)│   (1 daemon thread par watcher)&#xA;              └────────────────┬────────────────┘&#xA;                               │&#xA;                               ▼&#xA;              ┌─────────────────────────────────┐&#xA;              │  _run_chain(chain_runners, ...) │── retourne (result, should_retry)&#xA;              └────────────────┬────────────────┘&#xA;                               │&#xA;                               ▼&#xA;              ┌─────────────────────────────────┐&#xA;              │  watchers.stop()                │   (toujours)&#xA;              └────────────────┬────────────────┘&#xA;                               │&#xA;                               ▼&#xA;              ┌─────────────────────────────────┐&#xA;              │  teardown() (si non-None)       │   (TOUJOURS, même si chain a fail)&#xA;              └────────────────┬────────────────┘     (les exceptions sont logguées &amp;amp; avalées)&#xA;                               │&#xA;                               ▼&#xA;              ┌─────────────────────────────────┐&#xA;              │  steps_count = act_counter.get()│&#xA;              │  return Outcome(...)            │&#xA;              └─────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;execute&#34;&gt;&lt;code&gt;execute&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#execute&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;execute&lt;/span&gt;(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, test: Test[Driver], &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    driver: Driver,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    taxonomy: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;tuple&lt;/span&gt;[&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logger_with_taxonomy: ILogger,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logger_without_taxonomy: ILogger,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    attempt: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;, max_attempts: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; ExecutionOutcome:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    test_runner &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; test&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;spawn(driver, logger_with_taxonomy)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; test_runner&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;skipped:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; ExecutionOutcome(result&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;, skipped&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;True&lt;/span&gt;, setup_failed&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;False&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                should_retry&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;False&lt;/span&gt;, steps_count&lt;span style=&#34;color:#ff79c6&#34;&gt;=-&lt;/span&gt;&lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logger_without_taxonomy&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;test_name(test&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; test_runner&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;setup &lt;span style=&#34;color:#ff79c6&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;try&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            test_runner&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;setup()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;except&lt;/span&gt; Exception &lt;span style=&#34;color:#ff79c6&#34;&gt;as&lt;/span&gt; exc:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            msg &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;test&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt; -- Setup failed (attempt &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;attempt&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;max_attempts&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;): &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;exc&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            logger_with_taxonomy&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;warning(msg)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; test_runner&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;teardown &lt;span style=&#34;color:#ff79c6&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_run_teardown(test_runner&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;teardown, test_name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;test&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name, logger&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;logger_with_taxonomy)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; ExecutionOutcome(result&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;, skipped&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;False&lt;/span&gt;, setup_failed&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;True&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                    should_retry&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;True&lt;/span&gt;, steps_count&lt;span style=&#34;color:#ff79c6&#34;&gt;=-&lt;/span&gt;&lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    watchers: Sequence[Watcher[Driver]] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; test_runner&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;watchers &lt;span style=&#34;color:#ff79c6&#34;&gt;or&lt;/span&gt; []&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_start_watchers(watchers, driver&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;driver, test_name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;test&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name, taxonomy&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;taxonomy)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    result, should_retry &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_run_chain(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        test_runner&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;chain_runners, test_name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;test&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name, attempt&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;attempt,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        driver&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;driver, logger&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;logger_without_taxonomy,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        logger_with_taxonomy&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;logger_with_taxonomy, max_attempts&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;max_attempts,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_stop_watchers(watchers)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; test_runner&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;teardown &lt;span style=&#34;color:#ff79c6&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_run_teardown(test_runner&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;teardown, test_name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;test&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name, logger&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;logger_with_taxonomy)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    steps_count &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_act_counter&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;get()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; ExecutionOutcome(result&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;result, skipped&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;False&lt;/span&gt;, setup_failed&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;False&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            should_retry&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;should_retry, steps_count&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;steps_count)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;deux-loggers-pourquoi&#34;&gt;Deux loggers : pourquoi&lt;a class=&#34;anchor&#34; href=&#34;#deux-loggers-pourquoi&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;logger_with_taxonomy&lt;/code&gt; vs &lt;code&gt;logger_without_taxonomy&lt;/code&gt; :&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.10.02 — DriverBuilder[Driver]</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/02-driver-builder/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/02-driver-builder/</guid>
      <description>&lt;h1 id=&#34;021002driverbuilderdriver&#34;&gt;02.10.02 — &lt;code&gt;DriverBuilder[Driver]&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#021002driverbuilderdriver&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/infra/driver_builder.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/infra/driver_builder.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Encapsule la gestion du &lt;strong&gt;profil&lt;/strong&gt; (souvent une copie tmp d&amp;rsquo;un répertoire utilisateur) et produit la paire &lt;code&gt;(driver, dispose)&lt;/code&gt; attendue par la pool.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;pourquoi-un-builder&#34;&gt;Pourquoi un builder ?&lt;a class=&#34;anchor&#34; href=&#34;#pourquoi-un-builder&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Quand on construit un driver Selenium avec un &lt;em&gt;profil&lt;/em&gt;, il faut :&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Copier le profil&lt;/strong&gt; dans un dossier temporaire (sinon Firefox/Chrome locked sur le profil original).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Lancer le driver&lt;/strong&gt; en pointant vers le dossier temporaire.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;À la fin&lt;/strong&gt; : &lt;code&gt;driver.quit()&lt;/code&gt; &lt;em&gt;puis&lt;/em&gt; supprimer le dossier temporaire.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;&lt;code&gt;DriverBuilder&lt;/code&gt; factorise ces trois étapes :&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.11.02 — CliStore[TKeys] &#43; _CliField[T] &#43; phantom_validate</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/11-opinionated/02-cli-store-phantoms/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/11-opinionated/02-cli-store-phantoms/</guid>
      <description>&lt;h1 id=&#34;021102clistoretkeys--_clifieldt--phantom_validate&#34;&gt;02.11.02 — &lt;code&gt;CliStore[TKeys]&lt;/code&gt; + &lt;code&gt;_CliField[T]&lt;/code&gt; + &lt;code&gt;phantom_validate&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#021102clistoretkeys--_clifieldt--phantom_validate&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichiers source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/opinionated/cli/store.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/opinionated/cli/store.py&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/opinionated/cli/phantoms.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/opinionated/cli/phantoms.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Store &lt;strong&gt;write-once&lt;/strong&gt; des valeurs CLI parsées. Chaque champ valide à l&amp;rsquo;écriture. Le &lt;code&gt;TKeys: Literal[...]&lt;/code&gt; apporte de l&amp;rsquo;autocomplétion sur les clés.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;_clifieldtchamp-write-once&#34;&gt;&lt;code&gt;_CliField[T]&lt;/code&gt; — champ write-once&lt;a class=&#34;anchor&#34; href=&#34;#_clifieldtchamp-write-once&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_CliField&lt;/span&gt;[T]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;__init__&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, validate: ValidationChainBuilder[T]) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_value: T &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; _Unset &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; _UNSET&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_validate &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; validate&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;set&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, value: T) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;isinstance&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_value, _Unset):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;raise&lt;/span&gt; RuntimeError(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Value already set.&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_validate(_validate(value))&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;execute()&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;raise_if_invalid()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_value &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; value&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; T:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;isinstance&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_value, _Unset):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;raise&lt;/span&gt; RuntimeError(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Value not set yet.&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_value&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;1-sentinel-_unset&#34;&gt;1. Sentinel &lt;code&gt;_Unset&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#1-sentinel-_unset&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_Unset&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;pass&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;_UNSET &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; _Unset()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Pourquoi pas &lt;code&gt;None&lt;/code&gt; ? Parce que &lt;code&gt;None&lt;/code&gt; peut être une &lt;strong&gt;valeur légitime&lt;/strong&gt; (&lt;code&gt;--profile-path&lt;/code&gt; n&amp;rsquo;est pas obligatoire ; sa valeur après parse peut légitimement être &lt;code&gt;None&lt;/code&gt;). Une sentinelle dédiée distingue « &lt;em&gt;pas encore set&lt;/em&gt; » de « &lt;em&gt;set à None&lt;/em&gt; ».&lt;/p&gt;</description>
    </item>
    <item>
      <title>03.02 — Closures comme primitive d&#39;inversion de contrôle</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/03-functional/02-closures-ioc/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/03-functional/02-closures-ioc/</guid>
      <description>&lt;h1 id=&#34;0302closures-comme-primitive-dinversion-de-contrôle&#34;&gt;03.02 — Closures comme primitive d&amp;rsquo;inversion de contrôle&lt;a class=&#34;anchor&#34; href=&#34;#0302closures-comme-primitive-dinversion-de-contr%c3%b4le&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Pas de DI container. Pas de framework d&amp;rsquo;injection. &lt;strong&gt;Une closure est la primitive d&amp;rsquo;injection&lt;/strong&gt; d&amp;rsquo;Ocarina.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;lidée&#34;&gt;L&amp;rsquo;idée&lt;a class=&#34;anchor&#34; href=&#34;#lid%c3%a9e&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Une closure capture des valeurs dans son scope englobant. Quand on retourne une fonction depuis une autre fonction, les paramètres de l&amp;rsquo;extérieur sont &lt;strong&gt;disponibles à l&amp;rsquo;intérieur&lt;/strong&gt;, même après que la fonction extérieure ait rendu la main.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;make_handler&lt;/span&gt;(logger: ILogger) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; Callable[[&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;], FailureHandler]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;make_failure_handler&lt;/span&gt;(msg: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; FailureHandler:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;actual_handler&lt;/span&gt;(exc: Exception) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            logger&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;error(msg, exc&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;exc)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; actual_handler&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; make_failure_handler&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;C&amp;rsquo;est aussi du &lt;em&gt;currying&lt;/em&gt; : &lt;code&gt;make_handler(logger)(msg)(exc)&lt;/code&gt;. Chaque appel capture une nouvelle couche d&amp;rsquo;environnement.&lt;/p&gt;</description>
    </item>
    <item>
      <title>04.02 — Cram tests (prysk)</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/02-cram-prysk/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/02-cram-prysk/</guid>
      <description>&lt;h1 id=&#34;0402cram-tests-prysk&#34;&gt;04.02 — Cram tests (&lt;code&gt;prysk&lt;/code&gt;)&lt;a class=&#34;anchor&#34; href=&#34;#0402cram-tests-prysk&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Tests CLI au format &lt;em&gt;cram&lt;/em&gt; : un fichier &lt;code&gt;.t&lt;/code&gt; contient des commandes shell et leur sortie attendue. Outil utilisé : &lt;code&gt;prysk&lt;/code&gt; (réécriture moderne de &lt;code&gt;cram&lt;/code&gt;).&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;fichier-t&#34;&gt;Fichier &lt;code&gt;.t&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#fichier-t&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Valid args produce a deterministic parsed config&#xA;&#xA;  $ touch driver&#xA;  $ &amp;#34;$PYTHON&amp;#34; &amp;#34;$TESTDIR/_demo_cli.py&amp;#34; --browser firefox --driver-path driver --workers 3 --wait-timeout 20 --logger terminal&#xA;  browser=firefox&#xA;  driver_path=driver&#xA;  headless=True&#xA;  workers=3&#xA;  wait_timeout=20&#xA;  logger=terminal&#xA;  only=()&#xA;  exclude=()&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&#xA;&lt;li&gt;Première ligne : titre (commentaire pour l&amp;rsquo;humain).&lt;/li&gt;&#xA;&lt;li&gt;Ligne vide.&lt;/li&gt;&#xA;&lt;li&gt;Lignes commençant par &lt;code&gt; $&lt;/code&gt; : commande shell exécutée.&lt;/li&gt;&#xA;&lt;li&gt;Lignes suivantes (indent 2) : sortie attendue.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Si la sortie réelle diffère : le test fail.&lt;/p&gt;</description>
    </item>
    <item>
      <title>05.02 — Les routes de l&#39;Igoristan</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/05-igoristan/02-routes/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/05-igoristan/02-routes/</guid>
      <description>&lt;h1 id=&#34;0502les-routes-de-ligoristan&#34;&gt;05.02 — Les routes de l&amp;rsquo;Igoristan&lt;a class=&#34;anchor&#34; href=&#34;#0502les-routes-de-ligoristan&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;h2 id=&#34;srcconfigroutests&#34;&gt;&lt;code&gt;src/config/routes.ts&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#srcconfigroutests&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; __ROOT &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;/igoristan/&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; DASHBOARD &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;dashboard&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; __ROUTES &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  DONKEY_SAUSAGE_DETECTOR&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;donkey-sausage-eater-detector&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  DASHBOARD_NESTED&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;DASHBOARD&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;/nested`&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  RANDOM_LOADERS&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;random-loaders&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  SACRED_UPLOAD&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;sacred-upload&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  RANDOM_ERROR&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;random-error&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  CHAOTIC_FORM&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;chaotic-form&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  CORSICAMON&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;corsicamon&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  MADNESS&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;madness&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  DASHBOARD,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  HOME&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#ff79c6&#34;&gt;as&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; satisfies Routes;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; ROUTES &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; createRoutes(__ROUTES, __ROOT);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Route&lt;/th&gt;&#xA;          &lt;th&gt;URL&lt;/th&gt;&#xA;          &lt;th&gt;Rôle&lt;/th&gt;&#xA;          &lt;th&gt;Chaos volontaire&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;HOME&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/igoristan/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Page d&amp;rsquo;accueil avec liens vers les autres pages, autoplay audio&lt;/td&gt;&#xA;          &lt;td&gt; — &lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;RANDOM_LOADERS&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/igoristan/random-loaders&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Loaders pseudo-aléatoires&lt;/td&gt;&#xA;          &lt;td&gt;Loaders qui restent affichés un temps variable&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;SACRED_UPLOAD&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/igoristan/sacred-upload&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Formulaire d&amp;rsquo;upload &lt;code&gt;react-dropzone&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt; — &lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;DASHBOARD&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/igoristan/dashboard&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Login (mot de passe &lt;code&gt;figatellu&lt;/code&gt;), MFA OTP optionnel&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;useAuth&lt;/code&gt; peut échouer 10% du temps même avec le bon mdp&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;DASHBOARD_NESTED&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/igoristan/dashboard/nested&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Page protégée, requiert MFA&lt;/td&gt;&#xA;          &lt;td&gt; — &lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;CORSICAMON&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/igoristan/corsicamon&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;Pokédex corse&lt;/em&gt; », 3 picks aléatoires via API key&lt;/td&gt;&#xA;          &lt;td&gt;1/5 chance de lever &lt;code&gt;Error(&#39;lol&#39;)&lt;/code&gt; artificiellement&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;RANDOM_ERROR&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/igoristan/random-error&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Page d&amp;rsquo;erreur factice&lt;/td&gt;&#xA;          &lt;td&gt;Titre matché par &lt;code&gt;ERROR_PAGE_REGEX&lt;/code&gt; côté tests&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;CHAOTIC_FORM&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/igoristan/chaotic-form&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Formulaire instable&lt;/td&gt;&#xA;          &lt;td&gt;Éléments &lt;code&gt;.catch-me-if-you-can&lt;/code&gt; apparaissent aléatoirement&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;MADNESS&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/igoristan/madness&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Histoires alternées (Cors, ThisIsBastia)&lt;/td&gt;&#xA;          &lt;td&gt;Rend l&amp;rsquo;une OU l&amp;rsquo;autre au hasard&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;DONKEY_SAUSAGE_DETECTOR&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/igoristan/donkey-sausage-eater-detector&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Détecteur de sales siciliens de merde&lt;/td&gt;&#xA;          &lt;td&gt;30% d&amp;rsquo;échec aléatoire (BSOD-style)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;page-par-page&#34;&gt;Page par page&lt;a class=&#34;anchor&#34; href=&#34;#page-par-page&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; links &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  { href: &lt;span style=&#34;color:#8be9fd&#34;&gt;ROUTES.RANDOM_LOADERS&lt;/span&gt;, label&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Random loaders&amp;#34;&lt;/span&gt; },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  { href: &lt;span style=&#34;color:#8be9fd&#34;&gt;ROUTES.SACRED_UPLOAD&lt;/span&gt;, label&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Sacred upload&amp;#34;&lt;/span&gt; },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  { href: &lt;span style=&#34;color:#8be9fd&#34;&gt;ROUTES.DASHBOARD&lt;/span&gt;, label&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Dashboard&amp;#34;&lt;/span&gt; },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  { href: &lt;span style=&#34;color:#8be9fd&#34;&gt;ROUTES.CORSICAMON&lt;/span&gt;, label&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Corsicamon&amp;#34;&lt;/span&gt; },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  { href: &lt;span style=&#34;color:#8be9fd&#34;&gt;ROUTES.RANDOM_ERROR&lt;/span&gt;, label&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Random Error&amp;#34;&lt;/span&gt; },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  { href: &lt;span style=&#34;color:#8be9fd&#34;&gt;ROUTES.CHAOTIC_FORM&lt;/span&gt;, label&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Chaotic form&amp;#34;&lt;/span&gt; },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  { href: &lt;span style=&#34;color:#8be9fd&#34;&gt;ROUTES.MADNESS&lt;/span&gt;, label&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Madness&amp;#34;&lt;/span&gt; },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  { href: &lt;span style=&#34;color:#8be9fd&#34;&gt;ROUTES.DONKEY_SAUSAGE_DETECTOR&lt;/span&gt;, label&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Donkey Sausage Detector&amp;#34;&lt;/span&gt; },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;home&#34;&gt;home&lt;a class=&#34;anchor&#34; href=&#34;#home&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;8 liens vers les autres pages + autoplay audio Angelus de Jérusalem (amen 🙏).&lt;/p&gt;</description>
    </item>
    <item>
      <title>06.02 — GET /api/otp</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/06-tests-workers/02-otp-endpoint/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/06-tests-workers/02-otp-endpoint/</guid>
      <description>&lt;h1 id=&#34;0602get-apiotp&#34;&gt;06.02 — &lt;code&gt;GET /api/otp&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0602get-apiotp&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/tests-workers/blob/main/api/otp.ts&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;api/otp.ts&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;code&#34;&gt;Code&lt;a class=&#34;anchor&#34; href=&#34;#code&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; { generate } &lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;otplib&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;type&lt;/span&gt; { NextRequest } &lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;next/server&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; redis &lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;../lib/redis&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; isAuthorized &lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;../lib/isAuthorized&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; config &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; { runtime&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;edge&amp;#34;&lt;/span&gt; };&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; SECRET_QUERY_PARAM_KEY &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;secret&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; OTP_SECRET_MIN_LENGTH &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;32&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; OTP_SECRET_MAX_LENGTH &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;64&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; BASE32_ALPHABET &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ABCDEFGHIJKLMNOPQRSTUVWXYZ234567&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; OTP_TTL &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;60&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;5&lt;/span&gt;; &lt;span style=&#34;color:#6272a4&#34;&gt;// 5 minutes&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; OTP_HISTORY_TTL &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; OTP_TTL &lt;span style=&#34;color:#ff79c6&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;60&lt;/span&gt;; &lt;span style=&#34;color:#6272a4&#34;&gt;// 6 minutes&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; MAX_FREE_PAYLOAD_SIZE &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;4096&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; corsHeaders &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Access-Control-Allow-Origin&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;*&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Access-Control-Allow-Methods&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;GET, OPTIONS&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Access-Control-Allow-Headers&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;x-api-key, Content-Type&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#ff79c6&#34;&gt;as&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;function&lt;/span&gt; generateRandomBase32Secret(length: &lt;span style=&#34;color:#8be9fd&#34;&gt;number&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;32&lt;/span&gt;)&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#8be9fd&#34;&gt;string&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;let&lt;/span&gt; secret &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; randomValues &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;new&lt;/span&gt; Uint8Array(length);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  crypto.getRandomValues(randomValues);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;let&lt;/span&gt; i &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;&lt;/span&gt; length; i&lt;span style=&#34;color:#ff79c6&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    secret &lt;span style=&#34;color:#ff79c6&#34;&gt;+=&lt;/span&gt; BASE32_ALPHABET[randomValues[i] &lt;span style=&#34;color:#ff79c6&#34;&gt;%&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;32&lt;/span&gt;];&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; secret;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;function&lt;/span&gt; ensureSecretLength(secret: &lt;span style=&#34;color:#8be9fd&#34;&gt;string&lt;/span&gt;)&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#8be9fd&#34;&gt;string&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; cleaned &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; secret.toUpperCase().replace(&lt;span style=&#34;color:#f1fa8c&#34;&gt;/\s/g&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; (cleaned.length &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;gt;&lt;/span&gt; OTP_SECRET_MAX_LENGTH) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; validLength &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;Math&lt;/span&gt;.floor(OTP_SECRET_MAX_LENGTH &lt;span style=&#34;color:#ff79c6&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;8&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;8&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; cleaned.substring(&lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt;, validLength);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; (cleaned.length &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;gt;=&lt;/span&gt; OTP_SECRET_MIN_LENGTH) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; validLength &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;Math&lt;/span&gt;.floor(cleaned.length &lt;span style=&#34;color:#ff79c6&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;8&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;8&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; cleaned.substring(&lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt;, validLength);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; repetitions &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;Math&lt;/span&gt;.ceil(OTP_SECRET_MIN_LENGTH &lt;span style=&#34;color:#ff79c6&#34;&gt;/&lt;/span&gt; cleaned.length);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; repeated &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; cleaned.repeat(repetitions);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; validLength &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;Math&lt;/span&gt;.floor(repeated.length &lt;span style=&#34;color:#ff79c6&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;8&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;8&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; repeated.substring(&lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;Math&lt;/span&gt;.max(validLength, OTP_SECRET_MIN_LENGTH));&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;default&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;function&lt;/span&gt; handler(request: &lt;span style=&#34;color:#8be9fd&#34;&gt;NextRequest&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; (request.method &lt;span style=&#34;color:#ff79c6&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;OPTIONS&amp;#34;&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;new&lt;/span&gt; Response(&lt;span style=&#34;color:#ff79c6&#34;&gt;null&lt;/span&gt;, { status: &lt;span style=&#34;color:#8be9fd&#34;&gt;200&lt;/span&gt;, headers: &lt;span style=&#34;color:#8be9fd&#34;&gt;corsHeaders&lt;/span&gt; });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#ff79c6&#34;&gt;!&lt;/span&gt;isAuthorized(request)) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;new&lt;/span&gt; Response(JSON.stringify({ error&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Unauthorized&amp;#34;&lt;/span&gt; }), {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      status: &lt;span style=&#34;color:#8be9fd&#34;&gt;401&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      headers&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; { &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;, ...corsHeaders },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; { searchParams } &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;new&lt;/span&gt; URL(request.url);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; freePayload: &lt;span style=&#34;color:#8be9fd&#34;&gt;Record&lt;/span&gt;&amp;lt;&lt;span style=&#34;color:#ff79c6&#34;&gt;string&lt;/span&gt;, &lt;span style=&#34;color:#50fa7b&#34;&gt;string&lt;/span&gt;&amp;gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {};&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;let&lt;/span&gt; payloadSize &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  searchParams.forEach((value, key) &lt;span style=&#34;color:#ff79c6&#34;&gt;=&amp;gt;&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; (key &lt;span style=&#34;color:#ff79c6&#34;&gt;===&lt;/span&gt; SECRET_QUERY_PARAM_KEY) &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; entrySize &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;new&lt;/span&gt; TextEncoder().encode(key &lt;span style=&#34;color:#ff79c6&#34;&gt;+&lt;/span&gt; value).length;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; (payloadSize &lt;span style=&#34;color:#ff79c6&#34;&gt;+&lt;/span&gt; entrySize &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;gt;&lt;/span&gt; MAX_FREE_PAYLOAD_SIZE) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      payloadSize &lt;span style=&#34;color:#ff79c6&#34;&gt;+=&lt;/span&gt; entrySize;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    freePayload[key] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; value;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    payloadSize &lt;span style=&#34;color:#ff79c6&#34;&gt;+=&lt;/span&gt; entrySize;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; (payloadSize &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;gt;&lt;/span&gt; MAX_FREE_PAYLOAD_SIZE) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;new&lt;/span&gt; Response(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      JSON.stringify({&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        error&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Bad Request&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        message&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;`Free payload exceeds maximum size of &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;MAX_FREE_PAYLOAD_SIZE&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt; bytes (got &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;payloadSize&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt; bytes)`&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        status: &lt;span style=&#34;color:#8be9fd&#34;&gt;400&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        headers&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; { &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;, ...corsHeaders },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    );&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; rawSecret &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    searchParams.&lt;span style=&#34;color:#ff79c6&#34;&gt;get&lt;/span&gt;(SECRET_QUERY_PARAM_KEY) &lt;span style=&#34;color:#ff79c6&#34;&gt;??&lt;/span&gt; generateRandomBase32Secret();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; secret &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; ensureSecretLength(rawSecret);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; otpCode &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;await&lt;/span&gt; generate({ secret });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; now &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;Date&lt;/span&gt;.now();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; expiresAtMs &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; now &lt;span style=&#34;color:#ff79c6&#34;&gt;+&lt;/span&gt; OTP_TTL &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;1000&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; createdAtTimestampLackingMsPrecision &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;Date&lt;/span&gt;(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    now &lt;span style=&#34;color:#ff79c6&#34;&gt;-&lt;/span&gt; (now &lt;span style=&#34;color:#ff79c6&#34;&gt;%&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;1000&lt;/span&gt;),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ).toISOString();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; event &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ...freePayload,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    otpCode,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    secret,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    createdAtTimestampLackingMsPrecision,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    expiresAt: &lt;span style=&#34;color:#8be9fd&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;Date&lt;/span&gt;(expiresAtMs).toISOString(),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; key &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;`otp:&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;now&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;crypto.randomUUID()&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;`&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;await&lt;/span&gt; redis.&lt;span style=&#34;color:#ff79c6&#34;&gt;set&lt;/span&gt;(key, JSON.stringify(event), { ex: &lt;span style=&#34;color:#8be9fd&#34;&gt;OTP_HISTORY_TTL&lt;/span&gt; });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;new&lt;/span&gt; Response(JSON.stringify(event), {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    headers&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; { &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;, ...corsHeaders },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;flot&#34;&gt;Flot&lt;a class=&#34;anchor&#34; href=&#34;#flot&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;1. Request arrive&#xA;   ↓&#xA;2. Si OPTIONS → 200 + CORS headers (preflight)&#xA;   ↓&#xA;3. isAuthorized(request) ? Non → 401&#xA;   ↓&#xA;4. Parse searchParams ; tout sauf &amp;#34;secret&amp;#34; est mis dans freePayload&#xA;   ↓&#xA;5. payloadSize &amp;gt; 4 KB ? → 400&#xA;   ↓&#xA;6. secret = ?secret=&amp;lt;X&amp;gt; (si fourni) sinon random Base32 32-chars&#xA;   ↓&#xA;7. ensureSecretLength : assure 32..64 chars, multiple de 8&#xA;   ↓&#xA;8. otpCode = generate({ secret })   ← otplib TOTP RFC 6238&#xA;   ↓&#xA;9. createdAt = floor(now / 1000) * 1000  ← amputation des ms&#xA;   ↓&#xA;10. event = { ...freePayload, otpCode, secret, createdAt, expiresAt }&#xA;    ↓&#xA;11. key = `otp:&amp;lt;now&amp;gt;:&amp;lt;uuid&amp;gt;`&#xA;    ↓&#xA;12. redis.set(key, JSON.stringify(event), { ex: 360 })  ← TTL 6 min&#xA;    ↓&#xA;13. Response = JSON.stringify(event), 200&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;cors-headers&#34;&gt;CORS headers&lt;a class=&#34;anchor&#34; href=&#34;#cors-headers&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; corsHeaders &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Access-Control-Allow-Origin&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;*&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Access-Control-Allow-Methods&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;GET, OPTIONS&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Access-Control-Allow-Headers&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;x-api-key, Content-Type&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#ff79c6&#34;&gt;as&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;*&lt;/code&gt; origin : permet à l&amp;rsquo;Igoristan (depuis &lt;code&gt;https://mojo-molotov.github.io&lt;/code&gt;) d&amp;rsquo;appeler le service.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;GET, OPTIONS&lt;/code&gt; : seuls verbes supportés.&lt;/li&gt;&#xA;&lt;li&gt;Custom header &lt;code&gt;x-api-key&lt;/code&gt; autorisé (sinon le &lt;em&gt;browser&lt;/em&gt; bloquerait).&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;code&gt;as const&lt;/code&gt; rend l&amp;rsquo;objet &lt;code&gt;readonly&lt;/code&gt; côté TypeScript.&lt;/p&gt;</description>
    </item>
    <item>
      <title>07.02 — Les 5 adapters</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/02-adapters/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/02-adapters/</guid>
      <description>&lt;h1 id=&#34;0702les-5-adapters&#34;&gt;07.02 — Les 5 adapters&lt;a class=&#34;anchor&#34; href=&#34;#0702les-5-adapters&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Tout projet Ocarina commence par écrire &lt;strong&gt;cinq&lt;/strong&gt; adapters au-dessus du framework.&lt;br&gt;&#xA;Le projet &lt;code&gt;ocarina-example&lt;/code&gt; les fournit en référence.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;listing&#34;&gt;Listing&lt;a class=&#34;anchor&#34; href=&#34;#listing&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Adapter&lt;/th&gt;&#xA;          &lt;th&gt;Statut&lt;/th&gt;&#xA;          &lt;th&gt;Fichier&lt;/th&gt;&#xA;          &lt;th&gt;But&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;act&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;requis&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;lib/ext/ocarina/adapters/agnostic/act.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Hook &lt;code&gt;on_failure&lt;/code&gt; spécifique au projet&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;match_page&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;facultatif&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;lib/ext/ocarina/adapters/agnostic/match_page.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Politique d&amp;rsquo;exceptions partagée&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;EnvGetters&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;facultatif&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;lib/ext/ocarina/adapters/agnostic/env_getters.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Accès typé aux env vars&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestSuite&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;requis&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;lib/ext/ocarina/adapters/selenium/test_suite.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Façade : fige &lt;code&gt;transient_errors&lt;/code&gt;, &lt;code&gt;max_retries&lt;/code&gt;, &lt;code&gt;autoscreen&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestCampaign&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;requis&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;lib/ext/ocarina/adapters/selenium/test_campaign.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Façade : &lt;code&gt;max_workers&lt;/code&gt; depuis CLI&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;act&#34;&gt;&lt;code&gt;act&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#act&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# lib/ext/ocarina/adapters/agnostic/act.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; contextlib &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; suppress&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; ocarina.dsl.testing_with_railway.constructors.create_act &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; create_act&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; ocarina.railway.result &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; Fail&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; lib.custom_errors.http &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; HttpErrorPageReachedError&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; lib.ext.ocarina.regex.error_page &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; ERROR_PAGE_REGEX&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;act&lt;/span&gt;(pom: TPOM, action: Callable[[TPOM], TPOM]) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; ActionStart[TPOM]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;failure_hook&lt;/span&gt;(pom: TPOM, exc: Exception) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; Fail:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt; suppress(Exception):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            title &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; pom&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;get_current_title()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            is_http_error_page &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; title &lt;span style=&#34;color:#ff79c6&#34;&gt;and&lt;/span&gt; ERROR_PAGE_REGEX&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;match&lt;/span&gt;(title&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;strip())&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; is_http_error_page:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                http_error &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; HttpErrorPageReachedError(&lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;HTTP error page: &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;title&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                http_error&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;__cause__ &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; exc&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; Fail(error&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;http_error)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; Fail(error&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;exc)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; create_act(pom, action, on_failure&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;failure_hook)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;get_current_title()&lt;/code&gt;&lt;/strong&gt; : utilise la méthode obligatoire de &lt;code&gt;POMBase&lt;/code&gt; (cf. &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/08-pom-base/&#34;&gt;&lt;code&gt;../02-ocarina/08-pom-base.md&lt;/code&gt;&lt;/a&gt;).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;ERROR_PAGE_REGEX&lt;/code&gt;&lt;/strong&gt; : regex projet qui détecte un titre type page d&amp;rsquo;erreur HTTP (par exemple &lt;code&gt;^\d{3}\s.*&lt;/code&gt; pour &lt;code&gt;500 Internal Server Error&lt;/code&gt;).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;HttpErrorPageReachedError&lt;/code&gt;&lt;/strong&gt; : exception projet ; sous-classe d&amp;rsquo;Exception, sera dans &lt;code&gt;transient_errors&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;C&amp;rsquo;est cet adapter qui rend le rejeu &lt;strong&gt;automatique&lt;/strong&gt; pour les pages d&amp;rsquo;erreur.&lt;/p&gt;</description>
    </item>
    <item>
      <title>08.02 — CURA Healthcare (SUT)</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/08-ai-example/02-sut-cura/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/08-ai-example/02-sut-cura/</guid>
      <description>&lt;h1 id=&#34;0802cura-healthcare-sut&#34;&gt;08.02 — CURA Healthcare (SUT)&lt;a class=&#34;anchor&#34; href=&#34;#0802cura-healthcare-sut&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;h2 id=&#34;la-première-victime-docarina&#34;&gt;La première victime d&amp;rsquo;Ocarina&lt;a class=&#34;anchor&#34; href=&#34;#la-premi%c3%a8re-victime-docarina&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Hébergement&lt;/strong&gt; : &lt;a href=&#34;https://katalon-demo-cura.herokuapp.com/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;https://katalon-demo-cura.herokuapp.com/&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Source&lt;/strong&gt; : &lt;a href=&#34;https://github.com/katalon-studio/katalon-demo-cura&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;https://github.com/katalon-studio/katalon-demo-cura&lt;/a&gt; (PHP, open source, MIT)&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Hébergeur&lt;/strong&gt; : Heroku eco-dyno (qui s&amp;rsquo;endort après inactivité)&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Credentials&lt;/strong&gt; : &lt;code&gt;John Doe&lt;/code&gt; / &lt;code&gt;ThisIsNotAPassword&lt;/code&gt; (publics, hardcodés sur la page login)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;périmètre&#34;&gt;Périmètre&lt;a class=&#34;anchor&#34; href=&#34;#p%c3%a9rim%c3%a8tre&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Page&lt;/th&gt;&#xA;          &lt;th&gt;Fonction&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Marketing homepage&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;/profile.php#login&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Login&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;/appointment.php&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Form de booking (facility, programs, hospital_readmission, visit_date, comment)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;/confirmation.php&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Confirmation du booking&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;/history.php&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Historique des bookings du user&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;/profile.php#profile&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Page Profil (placeholder vide — gap §G-SPEC-2)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;pourquoi-cibler-cura&#34;&gt;Pourquoi cibler CURA&lt;a class=&#34;anchor&#34; href=&#34;#pourquoi-cibler-cura&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Open source&lt;/strong&gt;. On peut lire le PHP pour comprendre &lt;em&gt;pourquoi&lt;/em&gt; tel comportement.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Beaucoup de gaps&lt;/strong&gt;. Riche terrain de &lt;em&gt;findings&lt;/em&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Public, démo&lt;/strong&gt;. Pas d&amp;rsquo;autorisation à demander. Stable (maintenu par Katalon).&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;Plus une raison tacite : Katalon est un éditeur de framework de test concurrent (&lt;em&gt;Katalon Studio&lt;/em&gt;).&lt;br&gt;&#xA;C&amp;rsquo;est joliment &lt;em&gt;poétique&lt;/em&gt; de tester &lt;em&gt;leur&lt;/em&gt; app démo avec &lt;em&gt;Ocarina&lt;/em&gt;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>09.02 — I18n FR/EN/RU</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/02-i18n/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/02-i18n/</guid>
      <description>&lt;h1 id=&#34;0902i18n-frenru&#34;&gt;09.02 — I18n FR/EN/RU&lt;a class=&#34;anchor&#34; href=&#34;#0902i18n-frenru&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Toutes les pages du Holy Book existent en trois versions. EN à la racine de &lt;code&gt;docs/&lt;/code&gt;, FR sous &lt;code&gt;docs/fr/&lt;/code&gt;, RU sous &lt;code&gt;docs/ru/&lt;/code&gt;. VitePress route automatiquement.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;structure-des-fichiers&#34;&gt;Structure des fichiers&lt;a class=&#34;anchor&#34; href=&#34;#structure-des-fichiers&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;docs/&#xA;├── index.md                                                        # EN home&#xA;├── what-is-it.md&#xA;├── first-feedbacks.md&#xA;├── setup.md&#xA;├── scenarios-composability.md&#xA;├── datasets-smoke-tests-setup-teardown-proxy-api-and-caching.md&#xA;├── handling-flakiness.md&#xA;├── extensibility.md&#xA;├── using-ocarina-with-ai.md&#xA;│&#xA;├── fr/&#xA;│   ├── index.md                                                    # FR home&#xA;│   ├── what-is-it.md&#xA;│   ├── first-feedbacks.md&#xA;│   ├── setup.md&#xA;│   ├── scenarios-composability.md&#xA;│   ├── datasets-smoke-tests-setup-teardown-proxy-api-and-caching.md&#xA;│   ├── handling-flakiness.md&#xA;│   ├── extensibility.md&#xA;│   └── using-ocarina-with-ai.md&#xA;│&#xA;├── ru/&#xA;│   ├── index.md                                                    # RU home&#xA;│   ├── what-is-it.md&#xA;│   ├── first-feedbacks.md&#xA;│   ├── setup.md&#xA;│   ├── scenarios-composability.md&#xA;│   ├── datasets-smoke-tests-setup-teardown-proxy-api-and-caching.md&#xA;│   ├── handling-flakiness.md&#xA;│   ├── extensibility.md&#xA;│   └── using-ocarina-with-ai.md&#xA;│&#xA;├── public/&#xA;│   ├── favicon.ico&#xA;│   ├── logo.png&#xA;│   ├── robots.txt&#xA;│   ├── .nojekyll&#xA;│   ├── .spa&#xA;│   └── assets/&#xA;│       └── content/&#xA;│           └── ...                                                 # images partagées EN+FR+RU&#xA;│&#xA;└── .vitepress/&#xA;    ├── config.mts&#xA;    ├── blog-theme.ts&#xA;    ├── shims.d.ts&#xA;    ├── plugins/&#xA;    │   ├── llm.ts&#xA;    │   ├── pub.ts&#xA;    │   ├── skills.ts&#xA;    │   └── webp.ts&#xA;    └── theme/&#xA;        ├── index.ts&#xA;        ├── style.css&#xA;        ├── user-theme.css&#xA;        ├── components/&#xA;        │   ├── Pagination.vue&#xA;        │   ├── BlogHomeBanner.vue&#xA;        │   └── NotFound.vue&#xA;        └── assets/&#xA;            └── bg.png&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;routing&#34;&gt;Routing&lt;a class=&#34;anchor&#34; href=&#34;#routing&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Page&lt;/th&gt;&#xA;          &lt;th&gt;URL EN&lt;/th&gt;&#xA;          &lt;th&gt;URL FR&lt;/th&gt;&#xA;          &lt;th&gt;URL RU&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Page d&amp;rsquo;accueil&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/fr/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/ru/&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Pitch&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/what-is-it&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/fr/what-is-it&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/ru/what-is-it&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Coup de gueule&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/first-feedbacks&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/fr/first-feedbacks&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/ru/first-feedbacks&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Setup&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/setup&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/fr/setup&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/ru/setup&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&amp;hellip;&lt;/td&gt;&#xA;          &lt;td&gt;&amp;hellip;&lt;/td&gt;&#xA;          &lt;td&gt;&amp;hellip;&lt;/td&gt;&#xA;          &lt;td&gt;&amp;hellip;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;VitePress crée automatiquement le &lt;em&gt;switcher&lt;/em&gt; de langue (en haut à droite). Cliquer sur « &lt;em&gt;FR&lt;/em&gt; » depuis &lt;code&gt;/setup&lt;/code&gt; → &lt;code&gt;/fr/setup&lt;/code&gt;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>09.03.02 — Skills Analyse</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/03-skills/02-analyse/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/03-skills/02-analyse/</guid>
      <description>&lt;h1 id=&#34;090302skills-analyse&#34;&gt;09.03.02 — Skills Analyse&lt;a class=&#34;anchor&#34; href=&#34;#090302skills-analyse&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Analyses &lt;strong&gt;dynamiques&lt;/strong&gt; : utilisent les logs / les rapports d&amp;rsquo;une exécution récente pour diagnostiquer un échec. Deux skills de diagnostic de cause racine ouvrent la famille — &lt;code&gt;diagnose-root-cause&lt;/code&gt; pour un rouge déterministe, &lt;code&gt;diagnose-flake-root-cause&lt;/code&gt; pour un échec intermittent — et aiguillent vers les expériences contrôlées &lt;code&gt;analyse-*&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;listing-potentiellement-non-exhaustif&#34;&gt;Listing (potentiellement non exhaustif)&lt;a class=&#34;anchor&#34; href=&#34;#listing-potentiellement-non-exhaustif&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Skill&lt;/th&gt;&#xA;          &lt;th&gt;Cible&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;diagnose-root-cause&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Analyse de cause racine d&amp;rsquo;un rouge &lt;strong&gt;déterministe&lt;/strong&gt; : repart de zéro, remonte du synthétique vers le réel, cause triée en cinq familles&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;diagnose-flake-root-cause&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Analyse de cause racine d&amp;rsquo;un échec &lt;strong&gt;intermittent&lt;/strong&gt; (un flake) : taux d&amp;rsquo;échec, signature, corrélation ; orchestre les expériences &lt;code&gt;analyse-*&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;analyse-flakiness&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Élargit le filet des &lt;code&gt;transient_errors&lt;/code&gt; ; les morts chroniques sont de vraies flakes&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;analyse-fixture-flakiness&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Instrumente setup/teardown ; rend visibles les contaminations entre tests&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;analyse-watcher-flakiness&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Analyse la fiabilité des watchers (volume, dedupe, faux positifs)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;analyse-screenshot-flakiness&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Regroupe les captures d&amp;rsquo;écran par &lt;code&gt;(test, étape, navigateur)&lt;/code&gt;, détecte les différences&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;diagnose-root-cause&#34;&gt;&lt;code&gt;diagnose-root-cause&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#diagnose-root-cause&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;input  : un rouge déterministe (échoue à chaque exécution)&#xA;output : analyse de cause racine structurée :&#xA;            - reprend l&amp;#39;analyse de zéro, sans réutiliser les hypothèses précédentes&#xA;            - remonte du synthétique vers le réel (faux driver → vrai navigateur)&#xA;            - lit la source, confirme avec une sonde&#xA;            - cause racine triée en cinq familles&#xA;         passe la main à diagnose-flake-root-cause si l&amp;#39;échec se révèle intermittent&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;diagnose-flake-root-cause&#34;&gt;&lt;code&gt;diagnose-flake-root-cause&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#diagnose-flake-root-cause&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;input  : un échec intermittent (un flake)&#xA;output : analyse de cause racine fondée sur la distribution :&#xA;            - établit un taux d&amp;#39;échec (la distribution est la preuve)&#xA;            - fixe la signature, corrèle&#xA;            - aiguille vers la bonne expérience analyse-*, augmente le taux pour confirmer&#xA;            - classe en cinq familles de flakes&#xA;         l&amp;#39;orchestrateur de la famille analyse-*&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;analyse-flakiness&#34;&gt;&lt;code&gt;analyse-flakiness&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#analyse-flakiness&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;input  : logs des N dernières exécutions&#xA;output : liste de tests qui retry souvent (par % de retries)&#xA;         + recommandation :&#xA;            - ajouter une exception au tuple transient_errors&#xA;            - investiguer un test particulier (peut-être que le problème vient directement du test)&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;analyse-fixture-flakiness&#34;&gt;&lt;code&gt;analyse-fixture-flakiness&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#analyse-fixture-flakiness&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;input  : tests avec setup/teardown&#xA;output : tests dont le setup fail souvent&#xA;         + recommandation :&#xA;            - vérifier l&amp;#39;idempotence du setup&#xA;            - voir si teardown propre (sinon contamination entre tests)&#xA;            - sortir le seed du setup vers une init manuelle si trop fragile&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;analyse-watcher-flakiness&#34;&gt;&lt;code&gt;analyse-watcher-flakiness&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#analyse-watcher-flakiness&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;input  : logs des watcher.report(...) sur N runs&#xA;output : watchers à reporting anormal :&#xA;            - trop d&amp;#39;émissions (problème de dedupe)&#xA;            - aucune émission (le watcher est inutile)&#xA;            - faux positifs (élément attendu reporté comme intrus)&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;analyse-screenshot-flakiness&#34;&gt;&lt;code&gt;analyse-screenshot-flakiness&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#analyse-screenshot-flakiness&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;input  : screenshots de N runs, groupés par (test, step, browser)&#xA;output : flakiness visuelle :&#xA;            - tests dont les screenshots varient anormalement entre runs&#xA;            - tests qui passent mais dont l&amp;#39;aspect change&#xA;            - identifier si la variation est sémantique (vraie diff) ou cosmétique&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Utilise des heuristiques (hash, dimensions, palette dominante). Pas une vraie image diff.&lt;/p&gt;</description>
    </item>
    <item>
      <title>10.02 — Workflows ocarina (framework)</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/02-ocarina-workflows/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/02-ocarina-workflows/</guid>
      <description>&lt;h1 id=&#34;1002workflows-ocarina-framework&#34;&gt;10.02 — Workflows &lt;code&gt;ocarina&lt;/code&gt; (framework)&lt;a class=&#34;anchor&#34; href=&#34;#1002workflows-ocarina-framework&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Trois workflows : &lt;code&gt;main_ci.yml&lt;/code&gt; (production), &lt;code&gt;dev_ci.yml&lt;/code&gt; (branches dev/feature/fix), &lt;code&gt;unstable_python_full_build.yml&lt;/code&gt; (cron mensuel sur Python 3.15-dev).&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;main_ciyml&#34;&gt;&lt;code&gt;main_ci.yml&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#main_ciyml&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;trigger&#34;&gt;Trigger&lt;a class=&#34;anchor&#34; href=&#34;#trigger&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;on&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;push&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;branches&lt;/span&gt;: [main]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;pull_request&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;branches&lt;/span&gt;: [main]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;workflow_dispatch&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;inputs&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;python_versions&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Python versions to test&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;type&lt;/span&gt;: choice&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;required&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;default&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Stable only&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;options&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          - &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Stable only&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          - &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Stable + unstable (uses pre-built wheels)&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;workflow_dispatch&lt;/code&gt; avec un choix de version Python. Permet à l&amp;rsquo;utilisateur de &lt;em&gt;déclencher&lt;/em&gt; un build avec 3.15-dev (&lt;em&gt;pre-built wheels only&lt;/em&gt;) sans attendre le cron mensuel.&lt;/p&gt;</description>
    </item>
    <item>
      <title>11.02 — Auditabilité « en une après‑midi »</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/11-independence/02-auditability/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/11-independence/02-auditability/</guid>
      <description>&lt;h1 id=&#34;1102auditabilité-en-une-aprèsmidi&#34;&gt;11.02 — Auditabilité « &lt;em&gt;en une après‑midi&lt;/em&gt; »&lt;a class=&#34;anchor&#34; href=&#34;#1102auditabilit%c3%a9-en-une-apr%c3%a8smidi&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Engagement opérationnel : un humain doit pouvoir &lt;em&gt;lire&lt;/em&gt; tout le framework et le comprendre en quelques heures. Pas de magie cachée.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;engagement&#34;&gt;Engagement&lt;a class=&#34;anchor&#34; href=&#34;#engagement&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Citation du Holy Book (chapitre « &lt;em&gt;Qu&amp;rsquo;est donc Ocarina ?&lt;/em&gt; ») :&lt;/p&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Pour les équipes bloquées par des &lt;em&gt;politiques de sécurité&lt;/em&gt; : le code est petit, auditable en une après‑midi. Rien de caché. Les seules dépendances externes sont dans les plugins post‑exécution et si l&amp;rsquo;une d&amp;rsquo;elles ne passe pas, elle se retire sans que le reste ne casse.&lt;/p&gt;</description>
    </item>
    <item>
      <title>12.02 — LulzSec, le Lulzboat, AntiSec</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/02-lulzsec-lulzboat-antisec/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/02-lulzsec-lulzboat-antisec/</guid>
      <description>&lt;h1 id=&#34;1202lulzsec-le-lulzboat-antisec&#34;&gt;12.02 — LulzSec, le Lulzboat, AntiSec&lt;a class=&#34;anchor&#34; href=&#34;#1202lulzsec-le-lulzboat-antisec&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Trois sujets distincts : (1) &lt;strong&gt;LulzSec&lt;/strong&gt; en tant que groupe, (2) &lt;strong&gt;le Lulzboat&lt;/strong&gt; en tant que métaphore identitaire, (3) &lt;strong&gt;AntiSec&lt;/strong&gt; en tant que mouvement (deux versions : 1999 et 2011). Le Holy Book les invoque d&amp;rsquo;un même souffle ; ils ont chacun leur histoire.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;1-antisec-1999le-mouvement-originel&#34;&gt;1. AntiSec (1999) — le mouvement originel&lt;a class=&#34;anchor&#34; href=&#34;#1-antisec-1999le-mouvement-originel&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;origine&#34;&gt;Origine&lt;a class=&#34;anchor&#34; href=&#34;#origine&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;À la fin des années 1990, l&amp;rsquo;industrie de la sécurité informatique se &lt;em&gt;professionnalise&lt;/em&gt; : des entreprises naissent autour de la vente de firewalls, d&amp;rsquo;antivirus, d&amp;rsquo;audits. Une norme émerge : la &lt;strong&gt;&lt;em&gt;full disclosure&lt;/em&gt;&lt;/strong&gt;, publier publiquement les vulnérabilités, leurs PoC, leurs exploits, avec comme argument que cela force les éditeurs à patcher rapidement.&lt;/p&gt;</description>
    </item>
    <item>
      <title>99.02 — Index des fichiers cités</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/99-references/02-file-index/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/99-references/02-file-index/</guid>
      <description>&lt;h1 id=&#34;9902index-des-fichiers-cités&#34;&gt;99.02 — Index des fichiers cités&lt;a class=&#34;anchor&#34; href=&#34;#9902index-des-fichiers-cit%c3%a9s&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Par dépôt, les fichiers source explicitement cités dans le précis. URLs GitHub directs.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;mojo-molotovocarinaframework&#34;&gt;&lt;code&gt;mojo-molotov/ocarina&lt;/code&gt; — Framework&lt;a class=&#34;anchor&#34; href=&#34;#mojo-molotovocarinaframework&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;racine&#34;&gt;Racine&lt;a class=&#34;anchor&#34; href=&#34;#racine&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/pyproject.toml&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;pyproject.toml&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/Makefile&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;Makefile&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/mypy.ini&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;mypy.ini&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/.pre-commit-config.yaml&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;.pre-commit-config.yaml&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/allurerc.mjs&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;allurerc.mjs&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/README.md&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;srcocarinarailway&#34;&gt;&lt;code&gt;src/ocarina/railway/&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#srcocarinarailway&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/railway/result.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;result.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;srcocarinacustom_types&#34;&gt;&lt;code&gt;src/ocarina/custom_types/&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#srcocarinacustom_types&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/custom_types/effect.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;effect.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/custom_types/thunk.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;thunk.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/custom_types/tpom.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;tpom.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/custom_types/scenario.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;scenario.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/custom_types/test_components.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;test_components.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/custom_types/test_runner.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;test_runner.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/custom_types/oc_test_layers.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;oc_test_layers.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;srcocarinacustom_errorstest_framework&#34;&gt;&lt;code&gt;src/ocarina/custom_errors/test_framework/&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#srcocarinacustom_errorstest_framework&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/custom_errors/test_framework/no_matching_branch.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;no_matching_branch.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/custom_errors/test_framework/driver_died.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;driver_died.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/custom_errors/test_framework/pages.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;pages.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;srcocarinacustom_invariantstesting&#34;&gt;&lt;code&gt;src/ocarina/custom_invariants/testing/&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#srcocarinacustom_invariantstesting&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/custom_invariants/testing/workers.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;workers.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/custom_invariants/testing/oc_test_runners_ids.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;oc_test_runners_ids.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/custom_invariants/testing/oc_test_runners_names.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;oc_test_runners_names.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/custom_invariants/testing/oc_test_suites_names.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;oc_test_suites_names.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/custom_invariants/testing/oc_test_campaigns_names.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;oc_test_campaigns_names.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/custom_invariants/testing/oc_test_cycles_names.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;oc_test_cycles_names.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;srcocarinadslinvariants&#34;&gt;&lt;code&gt;src/ocarina/dsl/invariants/&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#srcocarinadslinvariants&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/invariants/validate.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;validate.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/invariants/assertions.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;assertions.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/invariants/errors.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;errors.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/invariants/internals/validation_chain.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;internals/validation_chain.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;srcocarinadsltesting&#34;&gt;&lt;code&gt;src/ocarina/dsl/testing/&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#srcocarinadsltesting&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing/oc_test.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;oc_test.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing/oc_test_suite.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;oc_test_suite.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing/oc_test_campaign.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;oc_test_campaign.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing/oc_test_cycle.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;oc_test_cycle.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing/watcher.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;watcher.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing/filter_tests_by_ids.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;filter_tests_by_ids.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing/internals/test_executor.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;internals/test_executor.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing/internals/test_flow.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;internals/test_flow.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing/selenium/create_test.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;selenium/create_test.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing/selenium/create_watcher.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;selenium/create_watcher.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;srcocarinadsltesting_with_railway&#34;&gt;&lt;code&gt;src/ocarina/dsl/testing_with_railway/&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#srcocarinadsltesting_with_railway&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing_with_railway/chain_actions.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;chain_actions.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing_with_railway/match_page.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;match_page.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing_with_railway/internals/action_chain.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;internals/action_chain.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing_with_railway/constructors/create_act.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;constructors/create_act.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;srcocarinaaggregates&#34;&gt;&lt;code&gt;src/ocarina/aggregates/&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#srcocarinaaggregates&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/aggregates/tests_layers.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;tests_layers.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;srcocarinainfra&#34;&gt;&lt;code&gt;src/ocarina/infra/&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#srcocarinainfra&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/infra/drivers_pool.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;drivers_pool.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/infra/driver_builder.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;driver_builder.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/infra/screenshotter.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;screenshotter.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/infra/act_counter.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;act_counter.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/infra/selenium/create_driver.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;selenium/create_driver.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/infra/selenium/create_drivers_pool.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;selenium/create_drivers_pool.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/infra/selenium/create_screenshotter.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;selenium/create_screenshotter.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/infra/selenium/driver_healthcheck.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;selenium/driver_healthcheck.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/infra/selenium/mixins.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;selenium/mixins.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;srcocarinaports&#34;&gt;&lt;code&gt;src/ocarina/ports/&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#srcocarinaports&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/ports/ilogger.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;ilogger.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/ports/itake_screenshot.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;itake_screenshot.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;srcocarinapom&#34;&gt;&lt;code&gt;src/ocarina/pom/&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#srcocarinapom&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/pom/base.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;base.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;srcocarinaopinionated&#34;&gt;&lt;code&gt;src/ocarina/opinionated/&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#srcocarinaopinionated&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/opinionated/infra/act_counter.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;infra/act_counter.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/opinionated/cli/builder.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;cli/builder.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/opinionated/cli/store.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;cli/store.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/opinionated/cli/phantoms.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;cli/phantoms.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/opinionated/cli/selenium/create_cli_store.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;cli/selenium/create_cli_store.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/opinionated/dsl/drive_page.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;dsl/drive_page.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/opinionated/launcher/bootstrap.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;launcher/bootstrap.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/opinionated/loggers/print_logger.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;loggers/print_logger.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/opinionated/loggers/file_logger.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;loggers/file_logger.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/opinionated/loggers/utils/format_metadata_str.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;loggers/utils/format_metadata_str.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/opinionated/plugins/reports/pretty_print_results.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;plugins/reports/pretty_print_results.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/opinionated/plugins/reports/results_to_json.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;plugins/reports/results_to_json.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/opinionated/plugins/reports/docx_tests_proofs.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;plugins/reports/docx_tests_proofs.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/opinionated/plugins/reports/timing.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;plugins/reports/timing.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;tests&#34;&gt;Tests&lt;a class=&#34;anchor&#34; href=&#34;#tests&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/tests/cram/cli_help.t&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;tests/cram/cli_help.t&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/tests/cram/cli_happy_path.t&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;tests/cram/cli_happy_path.t&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;(autres &lt;code&gt;.t&lt;/code&gt;)&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/tests/scenarios/conftest.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;tests/scenarios/conftest.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/tests/scenarios/test_railway_and_action_chain.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;tests/scenarios/test_railway_and_action_chain.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;(autres &lt;code&gt;test_*.py&lt;/code&gt;)&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/tests/dsl/invariants/test_types.yml&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;tests/dsl/invariants/test_types.yml&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/tests/dsl/testing_with_railway/test_types.yml&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;tests/dsl/testing_with_railway/test_types.yml&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;workflows&#34;&gt;Workflows&lt;a class=&#34;anchor&#34; href=&#34;#workflows&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/.github/workflows/main_ci.yml&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;.github/workflows/main_ci.yml&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/.github/workflows/dev_ci.yml&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;.github/workflows/dev_ci.yml&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/.github/workflows/unstable_python_full_build.yml&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;.github/workflows/unstable_python_full_build.yml&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/.github/actions/allure-history/action.yml&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;.github/actions/allure-history/action.yml&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;mojo-molotovocarina-example&#34;&gt;&lt;code&gt;mojo-molotov/ocarina-example&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#mojo-molotovocarina-example&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;code&#34;&gt;Code&lt;a class=&#34;anchor&#34; href=&#34;#code&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-example/blob/main/src/main.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/main.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-example/blob/main/src/tests/cycles/e2e.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/tests/cycles/e2e.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-example/blob/main/src/tests/scenarios/dashboard/access/happy_paths.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/tests/scenarios/dashboard/access/happy_paths.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-example/blob/main/src/lib/ext/ocarina/adapters/agnostic/act.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/lib/ext/ocarina/adapters/agnostic/act.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-example/blob/main/src/lib/ext/ocarina/adapters/agnostic/env_getters.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/lib/ext/ocarina/adapters/agnostic/env_getters.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-example/blob/main/src/lib/ext/selenium/humanize/proxy.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/lib/ext/selenium/humanize/proxy.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-example/blob/main/src/lib/ext/selenium/watchers/catch_me_if_you_can_watcher.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/lib/ext/selenium/watchers/catch_me_if_you_can_watcher.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-example/blob/main/src/lib/ext/redis/client.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/lib/ext/redis/client.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-example/blob/main/src/caches/l1.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/caches/l1.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-example/blob/main/src/caches/reserve_free_cache_key.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/caches/reserve_free_cache_key.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-example/blob/main/src/api/retrieve_dashboard_otp_code.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/api/retrieve_dashboard_otp_code.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;workflows-1&#34;&gt;Workflows&lt;a class=&#34;anchor&#34; href=&#34;#workflows-1&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-example/blob/main/.github/workflows/main_ci.yml&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;.github/workflows/main_ci.yml&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-example/blob/main/.github/workflows/dev_ci.yml&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;.github/workflows/dev_ci.yml&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-example/blob/main/.github/workflows/e2e.yml&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;.github/workflows/e2e.yml&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;mojo-molotovocarina-with-ai-example&#34;&gt;&lt;code&gt;mojo-molotov/ocarina-with-ai-example&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#mojo-molotovocarina-with-ai-example&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;docs&#34;&gt;Docs&lt;a class=&#34;anchor&#34; href=&#34;#docs&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-with-ai-example/blob/main/README.md&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-with-ai-example/blob/main/CLAUDE.md&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-with-ai-example/blob/main/CURA_FRD.md&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;CURA_FRD.md&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-with-ai-example/blob/main/CURA_TEST_STRATEGY.md&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;CURA_TEST_STRATEGY.md&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-with-ai-example/blob/main/IDENTIFIED_GAPS.md&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;IDENTIFIED_GAPS.md&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;code-1&#34;&gt;Code&lt;a class=&#34;anchor&#34; href=&#34;#code-1&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-with-ai-example/blob/main/src/main.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/main.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-with-ai-example/blob/main/src/tests/cycles/e2e.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/tests/cycles/e2e.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-with-ai-example/blob/main/src/tests/scenarios/_fragments/auth.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/tests/scenarios/_fragments/auth.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-with-ai-example/blob/main/src/tests/scenarios/appointment/past_date_booking.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/tests/scenarios/appointment/past_date_booking.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-with-ai-example/blob/main/src/lib/ext/ocarina/adapters/selenium/test_suite.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/lib/ext/ocarina/adapters/selenium/test_suite.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-with-ai-example/blob/main/src/lib/ext/ocarina/adapters/selenium/create_drivers_pool.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/lib/ext/ocarina/adapters/selenium/create_drivers_pool.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-with-ai-example/blob/main/src/lib/ext/ocarina/adapters/selenium/browser_navigation.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/lib/ext/ocarina/adapters/selenium/browser_navigation.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;workflows-2&#34;&gt;Workflows&lt;a class=&#34;anchor&#34; href=&#34;#workflows-2&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-with-ai-example/blob/main/.github/workflows/ai_proof_ci.yml&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;.github/workflows/ai_proof_ci.yml&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/ocarina-with-ai-example/blob/main/.github/workflows/ai_proof_e2e.yml&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;.github/workflows/ai_proof_e2e.yml&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;mojo-molotovigoristan&#34;&gt;&lt;code&gt;mojo-molotov/igoristan&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#mojo-molotovigoristan&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;code-2&#34;&gt;Code&lt;a class=&#34;anchor&#34; href=&#34;#code-2&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/igoristan/blob/main/src/config/routes.ts&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/config/routes.ts&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/igoristan/blob/main/src/config/brand.ts&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/config/brand.ts&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/igoristan/blob/main/src/hooks/useAuth.ts&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/hooks/useAuth.ts&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/igoristan/blob/main/src/components/LoginForm.tsx&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/components/LoginForm.tsx&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/igoristan/blob/main/src/pages/index/&amp;#43;Page.tsx&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/pages/index/+Page.tsx&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/igoristan/blob/main/src/pages/corsicamon/&amp;#43;Page.tsx&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/pages/corsicamon/+Page.tsx&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/igoristan/blob/main/src/pages/donkey-sausage-eater-detector/&amp;#43;Page.tsx&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/pages/donkey-sausage-eater-detector/+Page.tsx&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/igoristan/blob/main/src/pages/dashboard/&amp;#43;Page.tsx&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/pages/dashboard/+Page.tsx&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/igoristan/blob/main/vite.config.ts&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;vite.config.ts&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/igoristan/blob/main/package.json&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/igoristan/blob/main/eslint.config.ts&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;eslint.config.ts&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;workflows-3&#34;&gt;Workflows&lt;a class=&#34;anchor&#34; href=&#34;#workflows-3&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/igoristan/blob/main/.github/workflows/ci-pr.yml&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;.github/workflows/ci-pr.yml&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/igoristan/blob/main/.github/workflows/deploy.yml&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;.github/workflows/deploy.yml&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;mojo-molotovtests-workers&#34;&gt;&lt;code&gt;mojo-molotov/tests-workers&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#mojo-molotovtests-workers&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;code-3&#34;&gt;Code&lt;a class=&#34;anchor&#34; href=&#34;#code-3&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/tests-workers/blob/main/api/otp.ts&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;api/otp.ts&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/tests-workers/blob/main/api/otp-history.ts&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;api/otp-history.ts&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/tests-workers/blob/main/api/corsicadex.ts&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;api/corsicadex.ts&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/tests-workers/blob/main/lib/redis.ts&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;lib/redis.ts&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/tests-workers/blob/main/lib/isAuthorized.ts&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;lib/isAuthorized.ts&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/tests-workers/blob/main/consts/corsicadexData.ts&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;consts/corsicadexData.ts&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/tests-workers/blob/main/package.json&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/tests-workers/blob/main/vercel.json&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;vercel.json&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/mojo-molotov/tests-workers/blob/main/README.md&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;(Pas de workflow GitHub Actions.)&lt;/p&gt;</description>
    </item>
    <item>
      <title>00.03 — Flux d&#39;exécution global d&#39;une campagne e2e</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/00-big-picture/03-global-execution-flow/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/00-big-picture/03-global-execution-flow/</guid>
      <description>&lt;h1 id=&#34;0003flux-dexécution-global-dune-campagne-e2e&#34;&gt;00.03 — Flux d&amp;rsquo;exécution global d&amp;rsquo;une campagne e2e&lt;a class=&#34;anchor&#34; href=&#34;#0003flux-dex%c3%a9cution-global-dune-campagne-e2e&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;p&gt;Un &lt;code&gt;python -u src/main.py …&lt;/code&gt; lancé sur l&amp;rsquo;une des deux suites&#xA;(&lt;code&gt;ocarina-example&lt;/code&gt; ou &lt;code&gt;ocarina-with-ai-example&lt;/code&gt;) déclenche cette chaîne :&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;USER  ────────────────────────────────────────────────────────────────────&#xA;       python -u src/main.py --browser firefox --workers 3 [...]&#xA;              │&#xA;              ▼&#xA;       (1) parse CLI&#xA;           CliStoreSingleton.push(create_selenium_auto_cli_store())&#xA;              │&#xA;              ▼&#xA;       (2) build pool&#xA;           create_selenium_drivers_pool(max_size=N)&#xA;              │&#xA;              ▼&#xA;       (3) warm-up dépendances externes&#xA;           - Redis (ocarina-example)&#xA;           - Heroku dyno (ai-example, via curl --retry 6)&#xA;              │&#xA;              ▼&#xA;       (4) bootstrap(&#xA;              test_cycle  = create_e2e_test_cycle(drivers_pool),&#xA;              run_plugins = lambda results: run_plugins(&#xA;                                generate_docx_proof, generate_json_results,&#xA;                                exceptions_logger=...),&#xA;              post_exec   = pretty_print_results + sys.exit(1) si fail&#xA;           )&#xA;              │&#xA;OCARINA  ─────┼──────────────────────────────────────────────────────────&#xA;              │&#xA;              ▼&#xA;       (5) TestCycle.run_all (saturate_workers=True)&#xA;              ├─ smoke_tests_campaigns      [mode : fail-fast | wait-for-all]&#xA;              │     └─ TestCampaign.run_all&#xA;              │           └─ TestSuite.run (max_workers, saturate_workers)&#xA;              │                 └─ ThreadPoolExecutor → TestFlow.run&#xA;              │                       └─ pool.acquire() → TestExecutor.execute&#xA;              │                             ├─ setup()                  (optionnel)&#xA;              │                             ├─ watchers.start()         (daemon threads)&#xA;              │                             ├─ chain_runners            (DSL Railway)&#xA;              │                             ├─ watchers.stop()&#xA;              │                             └─ teardown()               (toujours)&#xA;              ├─ campaigns (main)&#xA;              │  (skippées si un smoke a fail)&#xA;              │&#xA;SUT   ────────┼────────────────────────────────────────────────────────────&#xA;              │&#xA;              ▼&#xA;       Selenium WebDriver ⇄ navigateur réel&#xA;       parfois : OTP HTTP GET + Redis (côté ocarina-example)&#xA;              │&#xA;              ▼&#xA;       (6) run_plugins(results)&#xA;              ├─ generate_docx_proof  (lit l&amp;#39;arbre de logs, fabrique des .docx)&#xA;              ├─ generate_json_results (sérialise results en .json)&#xA;              ├─ d&amp;#39;autres si déclarés (parallélisés via ThreadPoolExecutor)&#xA;              │&#xA;              ▼&#xA;       (6&amp;#39;) post_exec(results)&#xA;              ├─ pretty_print_results (ANSI, hiérarchique)&#xA;              └─ has_test_cycle_failed → sys.exit(1) le cas échéant&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;détail-des-étapes&#34;&gt;Détail des étapes&lt;a class=&#34;anchor&#34; href=&#34;#d%c3%a9tail-des-%c3%a9tapes&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;1-parse-cli&#34;&gt;(1) Parse CLI&lt;a class=&#34;anchor&#34; href=&#34;#1-parse-cli&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;&lt;code&gt;create_selenium_auto_cli_store()&lt;/code&gt; détecte l&amp;rsquo;OS via &lt;code&gt;platform.system()&lt;/code&gt; :&lt;/p&gt;</description>
    </item>
    <item>
      <title>01.03 — KISS et complexité ostentatoire</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/01-philosophy/03-kiss-and-complexity/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/01-philosophy/03-kiss-and-complexity/</guid>
      <description>&lt;h1 id=&#34;0103kiss-et-complexité-ostentatoire&#34;&gt;01.03 — KISS et complexité ostentatoire&lt;a class=&#34;anchor&#34; href=&#34;#0103kiss-et-complexit%c3%a9-ostentatoire&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;h2 id=&#34;la-critique-reçue&#34;&gt;La critique reçue&lt;a class=&#34;anchor&#34; href=&#34;#la-critique-re%c3%a7ue&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Le premier retour public reçu par Ocarina, &lt;em&gt;systématique&lt;/em&gt; d&amp;rsquo;après l&amp;rsquo;auteur :&lt;/p&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;La première « &lt;em&gt;critique&lt;/em&gt; » a souvent été la même : « &lt;em&gt;Tu te vantes de quelque chose qui est très simple.&lt;/em&gt; »&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;p&gt;Le Holy Book y répond longuement. Trois piliers : le &lt;strong&gt;caractère opérant&lt;/strong&gt; de la simplicité, la &lt;strong&gt;distinction simple ≠ peu élaboré&lt;/strong&gt;, et le &lt;strong&gt;refus de toutes les contre-propositions « &lt;em&gt;créatives&lt;/em&gt; »&lt;/strong&gt; reçues.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.03.03 — Le rail d&#39;échec : NeutralAction*</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/03-neutral-rail/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/03-neutral-rail/</guid>
      <description>&lt;h1 id=&#34;020303le-rail-déchecneutralaction&#34;&gt;02.03.03 — Le rail d&amp;rsquo;échec : &lt;code&gt;NeutralAction*&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#020303le-rail-d%c3%a9checneutralaction&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Cas particulier du builder : que se passe-t-il quand on &lt;em&gt;enchaîne&lt;/em&gt; une action sur un &lt;code&gt;ActionChain&lt;/code&gt; qui a déjà échoué ?&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;pourquoi-des-classes-neutral-plutôt-quun-if-côté-utilisateur&#34;&gt;Pourquoi des classes &lt;em&gt;Neutral&lt;/em&gt; plutôt qu&amp;rsquo;un &lt;code&gt;if&lt;/code&gt; côté utilisateur&lt;a class=&#34;anchor&#34; href=&#34;#pourquoi-des-classes-neutral-plut%c3%b4t-quun-if-c%c3%b4t%c3%a9-utilisateur&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Lorsqu&amp;rsquo;on enchaîne une action sur un &lt;code&gt;ActionChain&lt;/code&gt; qui a déjà échoué, on bascule dans trois classes &lt;em&gt;Neutral&lt;/em&gt; qui acceptent la même API, mais ne font rien.&lt;/p&gt;&#xA;&lt;p&gt;Imaginons que le DSL n&amp;rsquo;ait pas cette mécanique.&lt;br&gt;&#xA;Le code utilisateur ressemblerait à :&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.04.03 — .otherwise(...) et _any_of</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/04-invariants/03-otherwise-any-of/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/04-invariants/03-otherwise-any-of/</guid>
      <description>&lt;h1 id=&#34;020403otherwise-et-_any_of&#34;&gt;02.04.03 — &lt;code&gt;.otherwise(...)&lt;/code&gt; et &lt;code&gt;_any_of&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#020403otherwise-et-_any_of&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Comment Ocarina exprime un &lt;strong&gt;OR logique&lt;/strong&gt; entre deux prédicats sans casser l&amp;rsquo;agrégation d&amp;rsquo;erreurs.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;le-problème&#34;&gt;Le problème&lt;a class=&#34;anchor&#34; href=&#34;#le-probl%c3%a8me&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Cas concret : on veut « &lt;em&gt;&lt;code&gt;age == 18&lt;/code&gt; OR &lt;code&gt;age &amp;lt;= 65&lt;/code&gt;&lt;/em&gt; ». La syntaxe ROP/invariants est strictement chaîne d&amp;rsquo;assertions ; chaque &lt;code&gt;.assert_that()&lt;/code&gt; est un AND. Comment exprimer un OR ?&lt;/p&gt;&#xA;&lt;h2 id=&#34;code&#34;&gt;Code&lt;a class=&#34;anchor&#34; href=&#34;#code&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;otherwise&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, fallback: Predicate[T], &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, msg: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; ValidationAssertBlock[T]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_last_predicate &lt;span style=&#34;color:#ff79c6&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;raise&lt;/span&gt; RuntimeError(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;otherwise() must follow assert_that().&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    fallback_with_msg &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; _with_msg(fallback, msg, &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_name)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    combined &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; _any_of(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_last_predicate, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;([&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_otherwise_predicates, fallback_with_msg])&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_chain&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_steps&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;pop()                    &lt;span style=&#34;color:#6272a4&#34;&gt;# remplace le dernier step&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_chain&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;add_assertion(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_value, combined, &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_name)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_last_predicate &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; combined&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_otherwise_predicates&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;append(fallback_with_msg)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Guard&lt;/strong&gt;. &lt;code&gt;otherwise()&lt;/code&gt; doit suivre un &lt;code&gt;assert_that()&lt;/code&gt;. Sans prédicat précédent, on lève. (En pratique, le type-checker l&amp;rsquo;interdit déjà : &lt;code&gt;ValidationStartBlock.otherwise&lt;/code&gt; n&amp;rsquo;existe pas.)&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Combinaison&lt;/strong&gt;. On combine le prédicat précédent + tous les &lt;code&gt;otherwise&lt;/code&gt; déjà accumulés + le nouveau, via &lt;code&gt;_any_of&lt;/code&gt;. Le résultat est un prédicat composite.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Remplacement du step&lt;/strong&gt;. On &lt;em&gt;pop&lt;/em&gt; le dernier step du &lt;code&gt;_ValidationChain&lt;/code&gt; et on &lt;em&gt;push&lt;/em&gt; le step combiné. Sans ça, on aurait à la fois le prédicat original ET le combiné, donc l&amp;rsquo;erreur originale resurgirait.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;_any_of&#34;&gt;&lt;code&gt;_any_of&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#_any_of&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_any_of&lt;/span&gt;[T](&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;predicates: _PredicateWithMsg[T]) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; _PredicateWithMsg[T]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_try_predicate&lt;/span&gt;(p: _PredicateWithMsg[T], value: T) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; InvariantViolationError &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;try&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            p(value)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;except&lt;/span&gt; InvariantViolationError &lt;span style=&#34;color:#ff79c6&#34;&gt;as&lt;/span&gt; exc:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; exc&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;else&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;combined&lt;/span&gt;(value: T) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        errors &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; []&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; p &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; predicates:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            error &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; _try_predicate(p, value)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; error &lt;span style=&#34;color:#ff79c6&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt;                              &lt;span style=&#34;color:#6272a4&#34;&gt;# ✅ un prédicat passe → tout passe&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            errors&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;append(error)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        formatted_error_messages &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;  | &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;  | &amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;(e) &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; e &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; errors)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        msg &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; (&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;All predicates failed for value &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;value&lt;span style=&#34;color:#f1fa8c&#34;&gt;!r}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;» At least one of the following conditions must be satisfied:&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;formatted_error_messages&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;raise&lt;/span&gt; InvariantViolationError(msg)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; _PredicateWithMsg(combined)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;First success wins&lt;/strong&gt; : dès qu&amp;rsquo;un prédicat passe, &lt;code&gt;combined&lt;/code&gt; retourne &lt;code&gt;None&lt;/code&gt; (succès).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Si tous échouent&lt;/strong&gt; : on construit un message &lt;em&gt;agrégé&lt;/em&gt; listant &lt;strong&gt;toutes&lt;/strong&gt; les raisons d&amp;rsquo;échec, séparées par &lt;code&gt;|&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Le résultat est un &lt;code&gt;_PredicateWithMsg&lt;/code&gt;&lt;/strong&gt; : on peut le rebrancher dans la chaîne comme n&amp;rsquo;importe quel autre prédicat.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;L&amp;rsquo;agrégation est récursive&lt;/strong&gt; : si on a 3 &lt;code&gt;otherwise()&lt;/code&gt; successifs, le 3e combine &lt;code&gt;(P_orig OR P_otherwise1 OR P_otherwise2 OR P_otherwise3)&lt;/code&gt; en un seul prédicat.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;exemple&#34;&gt;Exemple&lt;a class=&#34;anchor&#34; href=&#34;#exemple&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;validate(age, name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;age&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;assert_that(is_equal_to(&lt;span style=&#34;color:#bd93f9&#34;&gt;18&lt;/span&gt;), msg&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Must be 18 or at most 65&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;otherwise(is_less_than_or_equal_to(&lt;span style=&#34;color:#bd93f9&#34;&gt;65&lt;/span&gt;), msg&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Must be 18 or at most 65&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;execute()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;raise_if_invalid()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;&#xA;&lt;li&gt;Si &lt;code&gt;age == 18&lt;/code&gt; : &lt;code&gt;is_equal_to(18)&lt;/code&gt; passe → OK.&lt;/li&gt;&#xA;&lt;li&gt;Si &lt;code&gt;age == 30&lt;/code&gt; : &lt;code&gt;is_equal_to(18)&lt;/code&gt; lève, &lt;code&gt;is_less_than_or_equal_to(65)&lt;/code&gt; passe → OK.&lt;/li&gt;&#xA;&lt;li&gt;Si &lt;code&gt;age == 70&lt;/code&gt; : les deux lèvent → &lt;code&gt;InvariantViolationError&lt;/code&gt; avec :&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;age: All predicates failed for value 70.&#xA;» At least one of the following conditions must be satisfied:&#xA;  | age: Must be 18 or at most 65&#xA;  | age: Must be 18 or at most 65&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;(Le doublon de message vient du fait qu&amp;rsquo;on a passé le même &lt;code&gt;msg=&lt;/code&gt; aux deux ; c&amp;rsquo;est volontaire ici, on &lt;em&gt;veut&lt;/em&gt; exprimer une intention unique.)&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.05.03 — TestFlow[Driver] — politique de rejeu</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/05-orchestration/03-test-flow-retries/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/05-orchestration/03-test-flow-retries/</guid>
      <description>&lt;h1 id=&#34;020503testflowdriverpolitique-de-rejeu&#34;&gt;02.05.03 — &lt;code&gt;TestFlow[Driver]&lt;/code&gt; — politique de rejeu&lt;a class=&#34;anchor&#34; href=&#34;#020503testflowdriverpolitique-de-rejeu&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing/internals/test_flow.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/testing/internals/test_flow.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Responsabilité unique&lt;/strong&gt; : pour un seul &lt;code&gt;Test&lt;/code&gt;, exécuter la boucle de rejeu, acquérir un driver propre à chaque tentative, et appliquer la politique de backoff.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;pourquoi-un-niveau-intermédiaire-entre-testsuite-et-testexecutor&#34;&gt;Pourquoi un niveau intermédiaire entre &lt;code&gt;TestSuite&lt;/code&gt; et &lt;code&gt;TestExecutor&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#pourquoi-un-niveau-interm%c3%a9diaire-entre-testsuite-et-testexecutor&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Niveau&lt;/th&gt;&#xA;          &lt;th&gt;Ne sait pas&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestExecutor&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Le rejeu, la pool de drivers&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;TestFlow&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;La concurrence inter-tests, l&amp;rsquo;agrégation suite-level&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestSuite&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;L&amp;rsquo;exécution d&amp;rsquo;une tentative, le rejeu&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;C&amp;rsquo;est une séparation &lt;em&gt;vraiment stricte&lt;/em&gt;. &lt;code&gt;TestFlow&lt;/code&gt; est le seul à connaître à la fois la pool et la politique de rejeu : son job est de &lt;em&gt;combiner&lt;/em&gt; les deux.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.10.03 — Screenshotter[TDriver]</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/03-screenshotter/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/03-screenshotter/</guid>
      <description>&lt;h1 id=&#34;021003screenshottertdriver&#34;&gt;02.10.03 — &lt;code&gt;Screenshotter[TDriver]&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#021003screenshottertdriver&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/infra/screenshotter.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/infra/screenshotter.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Utilitaire de capture d&amp;rsquo;écran générique, &lt;em&gt;thread-safe&lt;/em&gt;, agnostique du driver via un &lt;code&gt;Protocol&lt;/code&gt;, configurable via une &lt;code&gt;dataclass&lt;/code&gt;. Supporte le burst.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;protocol&#34;&gt;&lt;code&gt;Protocol&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#protocol&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;ScreenshotDriver&lt;/span&gt;(Protocol):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;save_screenshot&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, path: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;bool&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&amp;#34;Save standard viewport screenshot.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;C&amp;rsquo;est tout. &lt;em&gt;N&amp;rsquo;importe quel objet&lt;/em&gt; qui a une méthode &lt;code&gt;save_screenshot(path: str) -&amp;gt; bool&lt;/code&gt; est utilisable. Selenium WebDriver l&amp;rsquo;a nativement (&lt;code&gt;driver.save_screenshot&lt;/code&gt;), Playwright peut être wrappé, un fake driver en test l&amp;rsquo;expose trivialement.&lt;/p&gt;&#xA;&lt;h2 id=&#34;screenshotterconfigtdriver&#34;&gt;&lt;code&gt;ScreenshotterConfig[TDriver]&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#screenshotterconfigtdriver&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@dataclass(frozen&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;True&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;ScreenshotterConfig&lt;/span&gt;[TDriver: ScreenshotDriver]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    output_dir: Path&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    file_ext: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;.png&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    health_check: HealthCheck[TDriver] &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    save_full_page: SaveFullPageScreenshot[TDriver] &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    default_burst_delay: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;float&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;0.5&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    max_filename_retries: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;500&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    uuid_length: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;8&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Champ&lt;/th&gt;&#xA;          &lt;th&gt;Type&lt;/th&gt;&#xA;          &lt;th&gt;Rôle&lt;/th&gt;&#xA;          &lt;th&gt;Default&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;output_dir&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Path&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Répertoire des screenshots&lt;/td&gt;&#xA;          &lt;td&gt; — &lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;file_ext&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Extension (&lt;code&gt;.png&lt;/code&gt;, &lt;code&gt;.jpg&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;.png&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;health_check&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;HealthCheck[TDriver] | None&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;(driver) -&amp;gt; None&lt;/code&gt; qui lève si driver mort&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;save_full_page&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;SaveFullPageScreenshot[TDriver] | None&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;(driver, path) -&amp;gt; bool&lt;/code&gt; pour les screenshots pleine page (Firefox uniquement)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;default_burst_delay&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Délai entre 2 shots en mode burst&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;0.5&lt;/code&gt;s&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;max_filename_retries&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Max essais pour générer un nom unique&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;500&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;uuid_length&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Longueur du suffix UUID dans le nom&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;8&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;Immutable. On configure une fois et on exporte.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.11.03 — Auto CLI store &#43; flags &#43; validation</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/11-opinionated/03-selenium-cli/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/11-opinionated/03-selenium-cli/</guid>
      <description>&lt;h1 id=&#34;021103auto-cli-store--flags--validation&#34;&gt;02.11.03 — Auto CLI store + flags + validation&lt;a class=&#34;anchor&#34; href=&#34;#021103auto-cli-store--flags--validation&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/opinionated/cli/selenium/create_cli_store.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/opinionated/cli/selenium/create_cli_store.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;flags&#34;&gt;Flags&lt;a class=&#34;anchor&#34; href=&#34;#flags&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Flag&lt;/th&gt;&#xA;          &lt;th&gt;Default&lt;/th&gt;&#xA;          &lt;th&gt;Type&lt;/th&gt;&#xA;          &lt;th&gt;Validation&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;--driver-path&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;&amp;quot;&amp;quot;&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_file&lt;/code&gt; (sauf &lt;code&gt;--browser safari&lt;/code&gt;)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;--profile-path&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_none&lt;/code&gt; OR &lt;code&gt;is_dir&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;--browser&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;None&lt;/code&gt; (requis)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;choices&lt;/code&gt; argparse (par OS)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;--not-headless&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;False&lt;/code&gt; (= headless par défaut)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;bool&lt;/code&gt; (&lt;code&gt;store_true&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;phantom&lt;/em&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;--workers&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;5&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_positive&lt;/code&gt; + &lt;code&gt;is_not_zero&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;--logger&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;&amp;quot;terminal+file&amp;quot;&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;choices=LOGGERS_CHOICES&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;--wait-timeout&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;10&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;≤ 60&lt;/code&gt;, &lt;code&gt;&amp;gt; 0&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;--dont-force-delete-tmp-dirs&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;bool&lt;/code&gt; (&lt;code&gt;store_true&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;phantom&lt;/em&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;--only&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;[]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;list[str]&lt;/code&gt; (&lt;code&gt;nargs=&amp;quot;+&amp;quot;&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;phantom&lt;/em&gt; + mutex&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;--exclude&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;[]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;list[str]&lt;/code&gt; (&lt;code&gt;nargs=&amp;quot;+&amp;quot;&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;phantom&lt;/em&gt; + mutex&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;_DEFAULT_WORKERS_AMOUNT &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;5&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;_DEFAULT_LOGGER: SupportedLogger &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;terminal+file&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;_DEFAULT_BROWSER_AUTOMATION_TIMEOUT &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;10&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;_MAX_BROWSER_AUTOMATION_TIMEOUT &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;60&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;literal&#34;&gt;&lt;code&gt;Literal[...]&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#literal&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; SeleniumCliStoreKeys &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Literal[&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;driver_path&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;profile_path&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;browser&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;headless&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;workers&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;logger&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;wait_timeout&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;force_delete_tmp_dirs&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;only&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;exclude&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ces clés sont l&amp;rsquo;unique vocabulaire du store. Les call-sites les utilisent : &lt;code&gt;store.get(&amp;quot;workers&amp;quot;)&lt;/code&gt;, &lt;code&gt;store.get(&amp;quot;browser&amp;quot;)&lt;/code&gt;, etc.&lt;/p&gt;</description>
    </item>
    <item>
      <title>03.03 — Évaluation paresseuse</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/03-functional/03-lazy-evaluation/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/03-functional/03-lazy-evaluation/</guid>
      <description>&lt;h1 id=&#34;0303évaluation-paresseuse&#34;&gt;03.03 — Évaluation paresseuse&lt;a class=&#34;anchor&#34; href=&#34;#0303%c3%a9valuation-paresseuse&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Dans Ocarina, &lt;strong&gt;rien n&amp;rsquo;est exécuté tant qu&amp;rsquo;on ne l&amp;rsquo;a pas explicitement déclenché&lt;/strong&gt;. C&amp;rsquo;est ce qui rend les scénarios composables comme des valeurs.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;zzz&#34;&gt;Zzz&lt;a class=&#34;anchor&#34; href=&#34;#zzz&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Endroit&lt;/th&gt;&#xA;          &lt;th&gt;Forme&lt;/th&gt;&#xA;          &lt;th&gt;Déclencheur&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ChainRunner[T]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Thunk[ActionChain[T]]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;runner.run()&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;validate(...)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ValidationStartBlock&lt;/code&gt; / &lt;code&gt;ValidationAssertBlock&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;.execute()&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;match_page(...)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;retourne un &lt;code&gt;ChainRunner[Any]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;.run()&lt;/code&gt; (via la chaîne englobante)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;Watcher.callback&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Callable[[Watcher], None]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;_loop&lt;/code&gt; quand &lt;code&gt;start()&lt;/code&gt; est appelé&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;logger.set_prefix(thunk)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Thunk[str]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;recalculé à chaque appel de log&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;Scenario.setup&lt;/code&gt; / &lt;code&gt;teardown&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Effect&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;appelé par &lt;code&gt;TestExecutor&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;bootstrap(post_exec=...)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Callable[[TestCycleResults], None]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;appelé après &lt;code&gt;run_plugins&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;CliBuilder(effects_factory=lambda ns: (...))&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Effects&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;appelés après le parse argparse&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;test_scenario: TestScenario[Driver]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Callable[[Driver, ILogger], Scenario[Driver]]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;appelé par &lt;code&gt;Test.spawn&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;dispatch[mode]()&lt;/code&gt; dans &lt;code&gt;TestCycle.run_all&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;dict de &lt;code&gt;Thunk[bool]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;appelé en lookup&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;chainrunner&#34;&gt;&lt;code&gt;ChainRunner&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#chainrunner&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;runner &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; drive_page(act1, act2, act3)        &lt;span style=&#34;color:#6272a4&#34;&gt;# ⚠️  rien exécuté&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# … plus tard …&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;chain &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; runner&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;run()                         &lt;span style=&#34;color:#6272a4&#34;&gt;# ▶︎  exécution&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Capacité&lt;/th&gt;&#xA;          &lt;th&gt;Sans paresse&lt;/th&gt;&#xA;          &lt;th&gt;Avec paresse&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Stocker un scénario dans une variable&lt;/td&gt;&#xA;          &lt;td&gt;impossible (déjà exécuté)&lt;/td&gt;&#xA;          &lt;td&gt;trivial&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Multiplier &lt;code&gt;[runner] * 5&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;exécute 1 fois, on a 5 références au résultat&lt;/td&gt;&#xA;          &lt;td&gt;exécute 5 fois&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Passer un &lt;code&gt;runner&lt;/code&gt; à un autre &lt;code&gt;runner&lt;/code&gt; (composition)&lt;/td&gt;&#xA;          &lt;td&gt;impossible&lt;/td&gt;&#xA;          &lt;td&gt;trivial&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Réordonner les &lt;code&gt;act&lt;/code&gt; dans un test refactor&lt;/td&gt;&#xA;          &lt;td&gt;difficile&lt;/td&gt;&#xA;          &lt;td&gt;trivial&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;validateexecute&#34;&gt;&lt;code&gt;validate(...).execute()&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#validateexecute&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;v &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; validate(value, name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;x&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;assert_that(is_positive)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;assert_that(is_not_zero)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# … on peut composer …&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;combined &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; chain_validations(v, other_validation)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# … rien d&amp;#39;exécuté jusqu&amp;#39;ici …&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;combined&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;execute()&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;raise_if_invalid()        &lt;span style=&#34;color:#6272a4&#34;&gt;# ▶︎  exécution + agrégation&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;C&amp;rsquo;est ce qui permet à &lt;code&gt;_ValidationChain&lt;/code&gt; de &lt;strong&gt;collecter&lt;/strong&gt; toutes les erreurs avant d&amp;rsquo;en lever une seule (&lt;code&gt;AggregateInvariantViolationError&lt;/code&gt;).&lt;/p&gt;</description>
    </item>
    <item>
      <title>04.03 — Tests scénarios (pytest &#43; allure)</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/03-pytest-scenarios/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/03-pytest-scenarios/</guid>
      <description>&lt;h1 id=&#34;0403tests-scénarios-pytest--allure&#34;&gt;04.03 — Tests scénarios (pytest + allure)&lt;a class=&#34;anchor&#34; href=&#34;#0403tests-sc%c3%a9narios-pytest--allure&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;15 fichiers &lt;code&gt;test_*.py&lt;/code&gt; du dossier &lt;code&gt;tests/scenarios/&lt;/code&gt; couvrent le DSL, l&amp;rsquo;orchestration et l&amp;rsquo;acteur Playwright. Tests dynamiques.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;listing&#34;&gt;Listing&lt;a class=&#34;anchor&#34; href=&#34;#listing&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;tests/scenarios/&#xA;├── conftest.py                              # FakeDriver, RecordingPOM, make_*&#xA;├── test_railway_and_action_chain.py         # create_act, drive_page, Result, ChainRunner&#xA;├── test_match_page.py                       # match_page / when (premières / dernières branches, exceptions)&#xA;├── test_validate.py                         # validate, assert_that, otherwise, then, chain_validations&#xA;├── test_invariants_properties.py            # hypothesis (cf. 06-hypothesis-properties)&#xA;├── test_drivers_pool.py                     # WebDriversPool (acquire, warmup, shutdown, semaphore leaks)&#xA;├── test_screenshotter.py                    # Screenshotter (single shot, burst, healthcheck, threadsafety)&#xA;├── test_driver_builder.py                   # DriverBuilder (profile tmp dir, dispose)&#xA;├── test_watcher.py                          # Watcher (start, stop, callback errors swallowed, dedup cache)&#xA;├── test_playwright_driver_actor.py          # acteur PlaywrightDriver (sync_playwright mocké, sans navigateur)&#xA;├── test_playwright_adapter.py               # smoke navigateur réel (skip si pas de Chromium installé)&#xA;├── test_test_suite.py                       # TestSuite (parallel, saturation, only/exclude, transient_errors)&#xA;├── test_test_cycle_and_bootstrap.py         # TestCycle, mode fail-fast vs wait-for-all, bootstrap&#xA;├── test_loggers_and_reports.py              # PrintLogger, FileLogger, pretty_print, JSON&#xA;├── test_docx_tests_proofs.py                # generate_docx_proof (parse logs, insert images)&#xA;└── test_env.py                              # smoke test : versions, Python 3.14+&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;exemple-de-test-interne&#34;&gt;Exemple de test interne&lt;a class=&#34;anchor&#34; href=&#34;#exemple-de-test-interne&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@allure.epic(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Railway / action chain&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@allure.feature(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Action chain&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@allure.tag(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;act&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;handlers&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;happy-path&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@allure.severity(allure&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;severity_level&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;CRITICAL)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@allure.label(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;layer&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;unit&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@allure.title(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;A successful act reports success; the success handler fires once&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;test_success_act_fires_success_handler&lt;/span&gt;() &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    pom &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; RecordingPOM()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    calls &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;success&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;failure&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    chain &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; (&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        create_act(pom, &lt;span style=&#34;color:#ff79c6&#34;&gt;lambda&lt;/span&gt; p: p&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;step(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;click&amp;#34;&lt;/span&gt;))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;failure(&lt;span style=&#34;color:#ff79c6&#34;&gt;lambda&lt;/span&gt; _: calls&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;__setitem__&lt;/span&gt;(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;failure&amp;#34;&lt;/span&gt;, calls[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;failure&amp;#34;&lt;/span&gt;] &lt;span style=&#34;color:#ff79c6&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;success(&lt;span style=&#34;color:#ff79c6&#34;&gt;lambda&lt;/span&gt;: calls&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;__setitem__&lt;/span&gt;(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;success&amp;#34;&lt;/span&gt;, calls[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;success&amp;#34;&lt;/span&gt;] &lt;span style=&#34;color:#ff79c6&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;execute()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;assert&lt;/span&gt; chain&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;is_ok()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;assert&lt;/span&gt; pom&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;calls &lt;span style=&#34;color:#ff79c6&#34;&gt;==&lt;/span&gt; [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;click&amp;#34;&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;assert&lt;/span&gt; calls &lt;span style=&#34;color:#ff79c6&#34;&gt;==&lt;/span&gt; {&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;success&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;failure&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Assertion&lt;/th&gt;&#xA;          &lt;th&gt;Cible&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;chain.is_ok()&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;API publique&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;pom.calls == [&amp;quot;click&amp;quot;]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;comportement observable du POM (recording)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;calls == {&amp;quot;success&amp;quot;: 1, &amp;quot;failure&amp;quot;: 0}&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;bon handler appelé&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;annotations-allure&#34;&gt;Annotations Allure&lt;a class=&#34;anchor&#34; href=&#34;#annotations-allure&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Catégorie&lt;/th&gt;&#xA;          &lt;th&gt;Valeurs typiques&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;epic&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&amp;ldquo;Railway / action chain&amp;rdquo;, &amp;ldquo;Invariants&amp;rdquo;, &amp;ldquo;Orchestration&amp;rdquo;, &amp;ldquo;Watcher&amp;rdquo;, &amp;ldquo;Drivers pool&amp;rdquo;, &amp;ldquo;Reports&amp;rdquo;, &amp;ldquo;Match page&amp;rdquo;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;feature&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&amp;ldquo;Action chain&amp;rdquo;, &amp;ldquo;Validate&amp;rdquo;, &amp;ldquo;TestSuite&amp;rdquo;, &amp;ldquo;TestCycle&amp;rdquo;, &amp;ldquo;Bootstrap&amp;rdquo;, &amp;ldquo;Pretty print&amp;rdquo;, &amp;ldquo;JSON&amp;rdquo;, &amp;ldquo;DOCX proofs&amp;rdquo;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;tag&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&amp;ldquo;act&amp;rdquo;, &amp;ldquo;handlers&amp;rdquo;, &amp;ldquo;happy-path&amp;rdquo;, &amp;ldquo;error-handling&amp;rdquo;, &amp;ldquo;retry&amp;rdquo;, &amp;ldquo;transient&amp;rdquo;, &amp;ldquo;smoke&amp;rdquo;, &amp;ldquo;skip&amp;rdquo;, &amp;ldquo;saturation&amp;rdquo;, &amp;ldquo;parallel&amp;rdquo;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;severity&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;CRITICAL &amp;gt; NORMAL &amp;gt; MINOR &amp;gt; TRIVIAL&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;label(&amp;quot;layer&amp;quot;, ...)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&amp;ldquo;unit&amp;rdquo;, &amp;ldquo;integration&amp;rdquo;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;test_test_suitepy&#34;&gt;&lt;code&gt;test_test_suite.py&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#test_test_suitepy&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Tests passent en mode séquentiel (&lt;code&gt;max_workers=1&lt;/code&gt;).&lt;/li&gt;&#xA;&lt;li&gt;Tests passent en mode parallèle (&lt;code&gt;max_workers=N&lt;/code&gt;).&lt;/li&gt;&#xA;&lt;li&gt;Filtrage &lt;code&gt;--only&lt;/code&gt; / &lt;code&gt;--exclude&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Mutex &lt;code&gt;--only&lt;/code&gt; + &lt;code&gt;--exclude&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Saturation : tests clonés avec &lt;code&gt;[COPY N]&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Retries sur transient errors.&lt;/li&gt;&#xA;&lt;li&gt;Setup failure → SKIPPED si tous les attempts ratent dès le setup.&lt;/li&gt;&#xA;&lt;li&gt;Test &lt;code&gt;skipped=True&lt;/code&gt; s&amp;rsquo;arrête directement.&lt;/li&gt;&#xA;&lt;li&gt;Garde-fous (noms uniques, IDs uniques).&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;take_screenshot&lt;/code&gt; appelé sur fail si &lt;code&gt;autoscreen_on_fail&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Comptage exact des tentatives sur &lt;code&gt;max_retries&lt;/code&gt; (un test couvre le bord de la boucle).&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;test_match_pagepy&#34;&gt;&lt;code&gt;test_match_page.py&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#test_match_pagepy&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Cas&lt;/th&gt;&#xA;          &lt;th&gt;Assertion&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Première branche matche → exécutée&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;chain.is_ok()&lt;/code&gt;, branches suivantes non évaluées&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Deuxième branche matche → exécutée&lt;/td&gt;&#xA;          &lt;td&gt;id.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Aucune branche ne matche&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;NoMatchingBranchError&lt;/code&gt; dans le &lt;code&gt;Fail&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Branche matche, mais son &lt;code&gt;then&lt;/code&gt; fail&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Fail&lt;/code&gt; propagé&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;condition&lt;/code&gt; lève une &lt;code&gt;Exception&lt;/code&gt; ordinaire&lt;/td&gt;&#xA;          &lt;td&gt;Traité comme &lt;code&gt;False&lt;/code&gt;, branches suivantes essayées&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;condition&lt;/code&gt; lève une exception du tuple &lt;code&gt;raised_exceptions&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Re-raise&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Branche &lt;code&gt;then=[]&lt;/code&gt; matche&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Ok(None)&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;match_page&lt;/code&gt; au milieu d&amp;rsquo;un &lt;code&gt;test_chain&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Court-circuite si fail&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;test_drivers_poolpy&#34;&gt;&lt;code&gt;test_drivers_pool.py&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#test_drivers_poolpy&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Note : utilisation d&amp;rsquo;un &lt;code&gt;FakeDriverFactory&lt;/code&gt; qui peut être configuré pour lever / bloquer / dormir, et qui compte ses appels.&lt;/p&gt;</description>
    </item>
    <item>
      <title>05.03 — Le faux useAuth &#43; MFA OTP</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/05-igoristan/03-use-auth/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/05-igoristan/03-use-auth/</guid>
      <description>&lt;h1 id=&#34;0503le-faux-useauth--mfa-otp&#34;&gt;05.03 — Le faux &lt;code&gt;useAuth&lt;/code&gt; + MFA OTP&lt;a class=&#34;anchor&#34; href=&#34;#0503le-faux-useauth--mfa-otp&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Le hook &lt;code&gt;useAuth&lt;/code&gt; de l&amp;rsquo;Igoristan est volontairement faux. Il introduit deux mécaniques pour exercer le rejeu et la coordination des workers d&amp;rsquo;Ocarina : un échec aléatoire de 10% et un MFA OTP optionnel.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;code&#34;&gt;Code&lt;a class=&#34;anchor&#34; href=&#34;#code&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;// src/hooks/useAuth.ts&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; VALID_PASSWORD &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;figatellu&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; NOT_AUTHENTICATED &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; AUTHENTICATED_WITHOUT_MFA &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; AUTHENTICATED_WITH_MFA &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;2&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;// * ... This is FAKED&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; useAuth &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; () &lt;span style=&#34;color:#ff79c6&#34;&gt;=&amp;gt;&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; [_isAuthenticated, setIsAuthenticated] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; useLocalStorage(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;isAuthenticated&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    NOT_AUTHENTICATED,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  );&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; [isLoading, setIsLoading] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; useState(&lt;span style=&#34;color:#ff79c6&#34;&gt;true&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; isAuthenticated &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; useCallback(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    () &lt;span style=&#34;color:#ff79c6&#34;&gt;=&amp;gt;&lt;/span&gt; _isAuthenticated &lt;span style=&#34;color:#ff79c6&#34;&gt;!==&lt;/span&gt; NOT_AUTHENTICATED,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    [_isAuthenticated],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  );&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; isAuthenticatedWithMFA &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; useCallback(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    () &lt;span style=&#34;color:#ff79c6&#34;&gt;=&amp;gt;&lt;/span&gt; _isAuthenticated &lt;span style=&#34;color:#ff79c6&#34;&gt;===&lt;/span&gt; AUTHENTICATED_WITH_MFA,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    [_isAuthenticated],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  );&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; logout &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; useCallback(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    () &lt;span style=&#34;color:#ff79c6&#34;&gt;=&amp;gt;&lt;/span&gt; setIsAuthenticated(NOT_AUTHENTICATED),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    [setIsAuthenticated],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  );&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  useEffect(() &lt;span style=&#34;color:#ff79c6&#34;&gt;=&amp;gt;&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; delay &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;500&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt; randint(&lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#bd93f9&#34;&gt;5&lt;/span&gt;); &lt;span style=&#34;color:#6272a4&#34;&gt;// ◄── délai aléatoire 0.5-2.5s&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; timer &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; setTimeout(() &lt;span style=&#34;color:#ff79c6&#34;&gt;=&amp;gt;&lt;/span&gt; setIsLoading(&lt;span style=&#34;color:#ff79c6&#34;&gt;false&lt;/span&gt;), delay);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; () &lt;span style=&#34;color:#ff79c6&#34;&gt;=&amp;gt;&lt;/span&gt; clearTimeout(timer);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }, []);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; login: &lt;span style=&#34;color:#8be9fd&#34;&gt;LoginFn&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; ({ pre &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;false&lt;/span&gt;, password, withMFA }) &lt;span style=&#34;color:#ff79c6&#34;&gt;=&amp;gt;&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; (password &lt;span style=&#34;color:#ff79c6&#34;&gt;===&lt;/span&gt; VALID_PASSWORD &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;Math&lt;/span&gt;.random() &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;0.9&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#6272a4&#34;&gt;// ◄── 10% d&amp;#39;échec malgré bon mdp&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#ff79c6&#34;&gt;!&lt;/span&gt;pre) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        setIsAuthenticated(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          withMFA &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; AUTHENTICATED_WITH_MFA : &lt;span style=&#34;color:#8be9fd&#34;&gt;AUTHENTICATED_WITHOUT_MFA&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        );&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;true&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;false&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; { isAuthenticatedWithMFA, isAuthenticated, isLoading, logout, login };&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;mécaniques&#34;&gt;Mécaniques&lt;a class=&#34;anchor&#34; href=&#34;#m%c3%a9caniques&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;1-localstorage&#34;&gt;1. &lt;code&gt;LocalStorage&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#1-localstorage&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; [_isAuthenticated, setIsAuthenticated] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; useLocalStorage(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;isAuthenticated&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  NOT_AUTHENTICATED,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Trois valeurs : &lt;code&gt;-1&lt;/code&gt; (pas authentifié), &lt;code&gt;1&lt;/code&gt; (sans MFA), &lt;code&gt;2&lt;/code&gt; (avec MFA).&lt;br&gt;&#xA;Persistées dans &lt;code&gt;localStorage&lt;/code&gt;, faciles d&amp;rsquo;accès pour du test/debug manuel.&lt;/p&gt;</description>
    </item>
    <item>
      <title>06.03 — GET /api/otp-history</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/06-tests-workers/03-otp-history/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/06-tests-workers/03-otp-history/</guid>
      <description>&lt;h1 id=&#34;0603get-apiotp-history&#34;&gt;06.03 — &lt;code&gt;GET /api/otp-history&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0603get-apiotp-history&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/tests-workers/blob/main/api/otp-history.ts&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;api/otp-history.ts&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;code&#34;&gt;Code&lt;a class=&#34;anchor&#34; href=&#34;#code&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;type&lt;/span&gt; { NextRequest } &lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;next/server&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; redis &lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;../lib/redis&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; isAuthorized &lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;../lib/isAuthorized&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; config &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; { runtime&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;edge&amp;#34;&lt;/span&gt; };&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; corsHeaders &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Access-Control-Allow-Origin&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;*&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Access-Control-Allow-Methods&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;GET, OPTIONS&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Access-Control-Allow-Headers&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;x-api-key, Content-Type&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#ff79c6&#34;&gt;as&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;default&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;function&lt;/span&gt; handler(request: &lt;span style=&#34;color:#8be9fd&#34;&gt;NextRequest&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; (request.method &lt;span style=&#34;color:#ff79c6&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;OPTIONS&amp;#34;&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;new&lt;/span&gt; Response(&lt;span style=&#34;color:#ff79c6&#34;&gt;null&lt;/span&gt;, { status: &lt;span style=&#34;color:#8be9fd&#34;&gt;200&lt;/span&gt;, headers: &lt;span style=&#34;color:#8be9fd&#34;&gt;corsHeaders&lt;/span&gt; });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#ff79c6&#34;&gt;!&lt;/span&gt;isAuthorized(request)) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;new&lt;/span&gt; Response(JSON.stringify({ error&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Unauthorized&amp;#34;&lt;/span&gt; }), {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      status: &lt;span style=&#34;color:#8be9fd&#34;&gt;401&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      headers&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; { &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;, ...corsHeaders },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;let&lt;/span&gt; cursor &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;0&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; allEvents &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [];&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; BATCH_SIZE &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;100&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;do&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; [newCursor, keys] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;await&lt;/span&gt; redis.scan(cursor, {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      match&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;otp:*&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      count: &lt;span style=&#34;color:#8be9fd&#34;&gt;BATCH_SIZE&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cursor &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; newCursor;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; (keys.length &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; values &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;await&lt;/span&gt; redis.mget(...keys);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      allEvents.push(...values.filter(&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;Boolean&lt;/span&gt;));&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  } &lt;span style=&#34;color:#ff79c6&#34;&gt;while&lt;/span&gt; (cursor &lt;span style=&#34;color:#ff79c6&#34;&gt;!==&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;0&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;new&lt;/span&gt; Response(JSON.stringify(allEvents), {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    headers&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; { &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;, ...corsHeaders },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;mécanique-de-scan-redis&#34;&gt;Mécanique de &lt;code&gt;SCAN&lt;/code&gt; Redis&lt;a class=&#34;anchor&#34; href=&#34;#m%c3%a9canique-de-scan-redis&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;let&lt;/span&gt; cursor &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;0&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; allEvents &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [];&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; BATCH_SIZE &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;100&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;do&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; [newCursor, keys] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;await&lt;/span&gt; redis.scan(cursor, {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    match&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;otp:*&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    count: &lt;span style=&#34;color:#8be9fd&#34;&gt;BATCH_SIZE&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  cursor &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; newCursor;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; (keys.length &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; values &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;await&lt;/span&gt; redis.mget(...keys);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    allEvents.push(...values.filter(&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;Boolean&lt;/span&gt;));&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#ff79c6&#34;&gt;while&lt;/span&gt; (cursor &lt;span style=&#34;color:#ff79c6&#34;&gt;!==&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;0&amp;#34;&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;&#xA;&lt;li&gt;&lt;code&gt;cursor = &amp;quot;0&amp;quot;&lt;/code&gt; (initialisation).&lt;/li&gt;&#xA;&lt;li&gt;Boucle :&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;SCAN &amp;lt;cursor&amp;gt; MATCH otp:* COUNT 100&lt;/code&gt; retourne &lt;code&gt;[newCursor, keys]&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Si &lt;code&gt;keys&lt;/code&gt; non vide : &lt;code&gt;MGET k1 k2 ...&lt;/code&gt; récupère les valeurs.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;cursor = newCursor&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;On s&amp;rsquo;arrête quand &lt;code&gt;cursor === &amp;quot;0&amp;quot;&lt;/code&gt; (cycle terminé).&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;Pourquoi &lt;code&gt;SCAN&lt;/code&gt; plutôt que &lt;code&gt;KEYS&lt;/code&gt; ?&lt;/p&gt;</description>
    </item>
    <item>
      <title>07.03 — Scénarios Dashboard login</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/03-scenarios-login/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/03-scenarios-login/</guid>
      <description>&lt;h1 id=&#34;0703scénarios-dashboard-login&#34;&gt;07.03 — Scénarios Dashboard login&lt;a class=&#34;anchor&#34; href=&#34;#0703sc%c3%a9narios-dashboard-login&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Trois familles : happy paths, unhappy paths, data-driven multi-login. Tous exercent le &lt;code&gt;useAuth&lt;/code&gt; à 10% de raté et la coordination OTP.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;dashboard_login&#34;&gt;&lt;code&gt;dashboard_login&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#dashboard_login&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/tests/campaigns/dashboard_login.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;create_igoristan_login_campaign&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, drivers_pool: SeleniumWebDriversPool) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; TestCampaign:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; TestCampaign(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Dashboard login&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        suites&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;[&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            create_igoristan_login_happy_paths_test_suite(drivers_pool&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;drivers_pool),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            create_igoristan_login_unhappy_paths_test_suite(drivers_pool&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;drivers_pool),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            create_igoristan_login_data_driven_test_suite(drivers_pool&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;drivers_pool),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;happy-paths&#34;&gt;Happy paths&lt;a class=&#34;anchor&#34; href=&#34;#happy-paths&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/tests/suites/dashboard/access/happy_paths.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;create_igoristan_login_happy_paths_test_suite&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, drivers_pool) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; TestSuite:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; TestSuite(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Login happy paths&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        tests&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;[&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            test_dashboard_login_without_otp_happy_path,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            test_dashboard_login_with_otp_happy_path,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            test_dashboard_login_page_back_to_igoristan_button,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        drivers_pool&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;drivers_pool,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;test-1-login-sans-otp&#34;&gt;Test 1 : login sans OTP&lt;a class=&#34;anchor&#34; href=&#34;#test-1-login-sans-otp&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;dashboard_login_without_otp_happy_path&lt;/span&gt;(driver, logger):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    dashboard_creds &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; create_env_getters()&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;get_credentials(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;dashboard&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    on_dashboard_login_page &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; DashboardLoginPage(driver&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;driver)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    on_dashboard_welcome_page &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; DashboardWelcomePage(driver&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;driver)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    just_log_error &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; create_just_log_error(logger&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;logger)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    log_error_with_current_url &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; create_log_error_with_current_url(logger&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;logger, driver&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;driver)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    just_log_success &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; create_just_log_success(logger&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;logger)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    log_success_with_current_url_and_take_screenshot &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; create_log_success_with_current_url_and_take_screenshot(&lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    retries_amount &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;max&lt;/span&gt;(get_max_workers(), &lt;span style=&#34;color:#bd93f9&#34;&gt;10&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; [&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        drive_page(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(on_dashboard_login_page, open_dashboard_login_page)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;failure(just_log_error(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Failed to open the dashboard login page...&amp;#34;&lt;/span&gt;))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;success(just_log_success(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Opened the dashboard login page!&amp;#34;&lt;/span&gt;)),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(on_dashboard_login_page, verify_dashboard_login_page)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;failure(log_error_with_current_url(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Failed to verify...&amp;#34;&lt;/span&gt;))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;success(log_success_with_current_url_and_take_screenshot(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Verified!&amp;#34;&lt;/span&gt;)),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(on_dashboard_login_page,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                login_without_otp_and_with_retries(dashboard_creds, retries_amount, logger&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;logger))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;failure(just_log_error(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Failed to connect to the dashboard without OTP...&amp;#34;&lt;/span&gt;))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;success(just_log_success(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Connected to the dashboard!&amp;#34;&lt;/span&gt;)),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        drive_page(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(on_dashboard_welcome_page, verify_dashboard_welcome_page)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;failure(log_error_with_current_url(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Failed to verify...&amp;#34;&lt;/span&gt;))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;success(log_success_with_current_url_and_take_screenshot(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Verified!&amp;#34;&lt;/span&gt;)),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;retries_amount = max(get_max_workers(), 10)&lt;/code&gt;&lt;/strong&gt; : on rejoue le login interne (au POM) au moins 10 fois, à cause des 10% d&amp;rsquo;échec aléatoire.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;login_without_otp_and_with_retries(creds, retries_amount, logger=logger)&lt;/code&gt;&lt;/strong&gt; : closure qui capture creds + nb retries + logger. Le connector retourne &lt;code&gt;Callable[[DashboardLoginPage], DashboardLoginPage]&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;2 &lt;code&gt;drive_page&lt;/code&gt;&lt;/strong&gt; : login + welcome page. Chacun ouvre, vérifie, screenshot.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Log factories partout&lt;/strong&gt; : aucune lambda inline.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;SeleniumTitleMixin&lt;/code&gt; côté POM&lt;/strong&gt; : &lt;code&gt;get_current_title()&lt;/code&gt; est utilisé par le hook &lt;code&gt;on_failure&lt;/code&gt; de &lt;code&gt;act&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h3 id=&#34;test-2-login-avec-otp&#34;&gt;Test 2 : login avec OTP&lt;a class=&#34;anchor&#34; href=&#34;#test-2-login-avec-otp&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;dashboard_login_with_otp_happy_path&lt;/span&gt;(driver, logger):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#6272a4&#34;&gt;# ... setup pareil ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cache &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; in_memory_cache_with_30m_ttl&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    fresh_cache_key_for_username &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; reserve_free_cache_key(cache)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    fresh_cache_key_for_otp_send_button_click_date &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; reserve_free_cache_key(cache)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; [&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        drive_page(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(on_dashboard_login_page, open_dashboard_login_page)&lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(on_dashboard_login_page, verify_dashboard_login_page)&lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(on_dashboard_login_page,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                start_to_login_with_otp_and_with_retries(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    dashboard_creds, retries_amount,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    cache&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;cache, logger&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;logger,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    username_cache_key&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;fresh_cache_key_for_username,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    otp_send_button_click_date_cache_key&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;fresh_cache_key_for_otp_send_button_click_date,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                ))&lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(on_dashboard_login_page, verify_otp_screen)&lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(on_dashboard_login_page,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                type_otp_with_retries(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    retries_amount,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    cache&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;cache, logger&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;logger,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    username_cache_key&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;fresh_cache_key_for_username,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    otp_send_button_click_date_cache_key&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;fresh_cache_key_for_otp_send_button_click_date,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                ))&lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        drive_page(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(on_dashboard_welcome_page, verify_dashboard_welcome_page)&lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(on_dashboard_welcome_page, click_on_go_to_nested_page_btn)&lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        drive_page(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(on_dashboard_protected_page, verify_dashboard_protected_page)&lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Trois &lt;code&gt;drive_page&lt;/code&gt;. Cinq &lt;code&gt;act&lt;/code&gt; dans le premier. Cache L1 + clés réservées pour partager des valeurs entre acts (cf. &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/08-caches-locks/&#34;&gt;&lt;code&gt;08-caches-locks.md&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;</description>
    </item>
    <item>
      <title>08.03 — Documentation</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/08-ai-example/03-canonical-documents/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/08-ai-example/03-canonical-documents/</guid>
      <description>&lt;h1 id=&#34;0803documentation&#34;&gt;08.03 — Documentation&lt;a class=&#34;anchor&#34; href=&#34;#0803documentation&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Le projet IA est le plus documenté de l&amp;rsquo;écosystème d&amp;rsquo;Ocarina.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;listing&#34;&gt;Listing&lt;a class=&#34;anchor&#34; href=&#34;#listing&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Document&lt;/th&gt;&#xA;          &lt;th&gt;Statut&lt;/th&gt;&#xA;          &lt;th&gt;Lectorat&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Contrat de travail Claude ↔ projet&lt;/td&gt;&#xA;          &lt;td&gt;Claude + tout contributeur&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;CURA_FRD.md&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Spec reconstruite&lt;/td&gt;&#xA;          &lt;td&gt;Mainteneurs, stakeholders&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;CURA_TEST_STRATEGY.md&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Stratégie de test&lt;/td&gt;&#xA;          &lt;td&gt;Mainteneurs&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;IDENTIFIED_GAPS.md&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Inventaire technique des gaps&lt;/td&gt;&#xA;          &lt;td&gt;Mainteneurs&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;claudemd&#34;&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#claudemd&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Key documents&lt;/strong&gt; : pointe vers &lt;code&gt;README.md&lt;/code&gt;, &lt;code&gt;CURA_FRD.md&lt;/code&gt;, &lt;code&gt;CURA_TEST_STRATEGY.md&lt;/code&gt;, &lt;code&gt;IDENTIFIED_GAPS.md&lt;/code&gt;, &lt;code&gt;CLAUDE.local.md&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Project&lt;/strong&gt; : 2 phrases sur le projet (CURA, Python 3.14+, demo creds).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Project philosophy&lt;/strong&gt; : « &lt;em&gt;No tricks or hacks. Teach the pattern, not the symptom.&lt;/em&gt; »&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;CLAUDE.local.md&lt;/code&gt; template&lt;/strong&gt; : ce que doit contenir le fichier gitignored.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Layout&lt;/strong&gt; : arbre &lt;code&gt;src/&lt;/code&gt; complet, audité (commande shell qui vérifie que &lt;code&gt;CLAUDE.md&lt;/code&gt; et l&amp;rsquo;arbre réel sont synchronisés).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Ocarina hierarchy&lt;/strong&gt; : rappel &lt;code&gt;Test → Suite → Campaign → Cycle&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Test strategy&lt;/strong&gt; : structure du cycle, smoke gate, taxonomie.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Running tests&lt;/strong&gt; : commandes CLI.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Reports and screenshots&lt;/strong&gt; : règle « un screenshot par drive_page ».&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Scenario fragments&lt;/strong&gt; : quand extraire un fragment.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Data-driven tests&lt;/strong&gt; : quand utiliser le pattern, conventions de naming.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Mixins partagés&lt;/strong&gt; : &lt;code&gt;SeleniumBackAndForwardNavigationMixin&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Conventions&lt;/strong&gt; : 12 règles concrètes (TYPE_CHECKING guard, log factories, etc.).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Hard-won rules&lt;/strong&gt; : règles tirées de l&amp;rsquo;expérience, avec exemples du passé.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;hard-won-rules&#34;&gt;Hard-won rules&lt;a class=&#34;anchor&#34; href=&#34;#hard-won-rules&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;verify-sut-behaviourdon&#34;&gt;« &lt;em&gt;Verify SUT behaviour — don&amp;rsquo;t theorise&lt;/em&gt; »&lt;a class=&#34;anchor&#34; href=&#34;#verify-sut-behaviourdon&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;CURA is open source. Before building on a server-side claim, read the PHP:&lt;/p&gt;</description>
    </item>
    <item>
      <title>09.03.03 — Skills Black-hat</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/03-skills/03-black-hat/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/03-skills/03-black-hat/</guid>
      <description>&lt;h1 id=&#34;090303skills-black-hat-6&#34;&gt;09.03.03 — Skills Black-hat (6)&lt;a class=&#34;anchor&#34; href=&#34;#090303skills-black-hat-6&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;&lt;strong&gt;Idéations&lt;/strong&gt; de vulnérabilités de logique métier — pas d&amp;rsquo;exécution : « &lt;em&gt;security testing is functional and static, never active&lt;/em&gt; ».&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;listing-potentiellement-non-exhaustif&#34;&gt;Listing (potentiellement non exhaustif)&lt;a class=&#34;anchor&#34; href=&#34;#listing-potentiellement-non-exhaustif&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Skill&lt;/th&gt;&#xA;          &lt;th&gt;Cible&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;business-logic-vulnerability-ideation&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Faire tomber le produit via des chemins d&amp;rsquo;usage légitimes mais malicieux&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;incoherence-attack-ideation&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Chaque étape prise isolément a l&amp;rsquo;air innocente ; mais des combinaisons de ces étapes peuvent causer une incohérence dans le système&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;persistence-attack-ideation&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Tentatives répétées sur une action bloquée&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;permission-appropriateness-audit&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Le modèle d&amp;rsquo;accès est-il lui-même approprié ?&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;bfcache-exposure-ideation&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Attaques BFCache&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;lateral-resource-ideation&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;IDOR via la barre d&amp;rsquo;adresse uniquement&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;périmètre&#34;&gt;Périmètre&lt;a class=&#34;anchor&#34; href=&#34;#p%c3%a9rim%c3%a8tre&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Tous ces skills &lt;em&gt;imaginent&lt;/em&gt; des attaques que &lt;strong&gt;l&amp;rsquo;IA&lt;/strong&gt; pourrait formuler.&lt;/p&gt;</description>
    </item>
    <item>
      <title>10.03 — Workflows ocarina-example</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/03-example-workflows/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/03-example-workflows/</guid>
      <description>&lt;h1 id=&#34;1003workflows-ocarina-example&#34;&gt;10.03 — Workflows &lt;code&gt;ocarina-example&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#1003workflows-ocarina-example&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Voir aussi &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/11-ci/&#34;&gt;&lt;code&gt;../07-ocarina-example/11-ci.md&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;vue-densemble&#34;&gt;Vue d&amp;rsquo;ensemble&lt;a class=&#34;anchor&#34; href=&#34;#vue-densemble&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Workflow&lt;/th&gt;&#xA;          &lt;th&gt;Trigger&lt;/th&gt;&#xA;          &lt;th&gt;OS&lt;/th&gt;&#xA;          &lt;th&gt;Effet&lt;/th&gt;&#xA;          &lt;th&gt;Note&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;main_ci.yml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;push/PR main, dispatch&lt;/td&gt;&#xA;          &lt;td&gt;ubuntu + windows × py 3.14&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;make check-coding-style&lt;/code&gt; (mypy + ruff).&lt;/td&gt;&#xA;          &lt;td&gt;Gate PR rapide&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;dev_ci.yml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;push dev, dispatch&lt;/td&gt;&#xA;          &lt;td&gt;ubuntu × py 3.14&lt;/td&gt;&#xA;          &lt;td&gt;Idem&lt;/td&gt;&#xA;          &lt;td&gt;Pour branches dev&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;e2e.yml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;workflow_dispatch&lt;/code&gt; (env &lt;code&gt;OC&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;ubuntu + service Redis + Firefox + geckodriver 0.35.0&lt;/td&gt;&#xA;          &lt;td&gt;Run complet de la suite + upload &lt;code&gt;.screenshots/&lt;/code&gt; &lt;code&gt;.ocarina_logs/&lt;/code&gt; &lt;code&gt;.reports/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Manuel uniquement&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;vs-ocarinamain_ciyml&#34;&gt;vs &lt;code&gt;ocarina/main_ci.yml&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#vs-ocarinamain_ciyml&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Aspect&lt;/th&gt;&#xA;          &lt;th&gt;&lt;code&gt;ocarina/main_ci.yml&lt;/code&gt;&lt;/th&gt;&#xA;          &lt;th&gt;&lt;code&gt;ocarina-example/main_ci.yml&lt;/code&gt;&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Tests unitaires&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Oui (pytest + cram)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Non&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Allure history&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Oui&lt;/td&gt;&#xA;          &lt;td&gt;Non&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;GitHub Pages deploy&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Oui (Allure report)&lt;/td&gt;&#xA;          &lt;td&gt;Non&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Matrice OS&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Oui (ubuntu + windows)&lt;/td&gt;&#xA;          &lt;td&gt;Oui (idem)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Matrice Python&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Optionnel (3.14 ou 3.14+3.15)&lt;/td&gt;&#xA;          &lt;td&gt;Non (3.14 seul)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;→ &lt;code&gt;ocarina&lt;/code&gt; &lt;em&gt;se teste&lt;/em&gt; (tests unitaires) en CI. &lt;code&gt;ocarina-example&lt;/code&gt; n&amp;rsquo;est soumis qu&amp;rsquo;à des tests &lt;em&gt;statiques&lt;/em&gt; en CI ; ses tests dynamiques (e2e) sont déclenchés &lt;em&gt;à la demande&lt;/em&gt; via &lt;code&gt;e2e.yml&lt;/code&gt;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>11.03 — Refus explicites</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/11-independence/03-explicit-refusals/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/11-independence/03-explicit-refusals/</guid>
      <description>&lt;h1 id=&#34;1103refus-explicites&#34;&gt;11.03 — Refus explicites&lt;a class=&#34;anchor&#34; href=&#34;#1103refus-explicites&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Synthèse des refus dispersés dans le Holy Book.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;tableau-récapitulatif&#34;&gt;Tableau récapitulatif&lt;a class=&#34;anchor&#34; href=&#34;#tableau-r%c3%a9capitulatif&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Refus&lt;/th&gt;&#xA;          &lt;th&gt;Raison&lt;/th&gt;&#xA;          &lt;th&gt;Référence&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Selenium synchrone, Requests synchrone ; lisibilité ; aucun intérêt d&amp;rsquo;introduire une &lt;em&gt;event-loop&lt;/em&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Holy Book&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Plugin pytest&lt;/td&gt;&#xA;          &lt;td&gt;Indépendance, fidélité ISTQB&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;README&lt;/code&gt; Ocarina&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;DSL textuel (Robot, Gherkin)&lt;/td&gt;&#xA;          &lt;td&gt;Pas de couche de traduction&lt;/td&gt;&#xA;          &lt;td&gt;Holy Book&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Programmation réactive&lt;/td&gt;&#xA;          &lt;td&gt;Static scenarios, cache in-memory suffit&lt;/td&gt;&#xA;          &lt;td&gt;Holy Book&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Tricks et hacks&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;Teach the pattern, not the symptom&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; ai-example&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Contributions « &lt;em&gt;stylées&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;C&amp;rsquo;est ma voiture&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;Holy Book&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Tests sécurité actifs&lt;/td&gt;&#xA;          &lt;td&gt;Fonctionnel et statique uniquement, jamais d&amp;rsquo;attaques&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; ai-example&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;1-refus-dasyncawait&#34;&gt;1. Refus d&amp;rsquo;&lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#1-refus-dasyncawait&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Citation centrale :&lt;/p&gt;</description>
    </item>
    <item>
      <title>12.03 — YTCracker, Nerdcore, Digital Gangster</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/03-ytcracker-nerdcore-digital-gangster/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/03-ytcracker-nerdcore-digital-gangster/</guid>
      <description>&lt;h1 id=&#34;1203ytcracker-nerdcore-digital-gangster&#34;&gt;12.03 — YTCracker, Nerdcore, Digital Gangster&lt;a class=&#34;anchor&#34; href=&#34;#1203ytcracker-nerdcore-digital-gangster&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Bryce Case Jr., un seul homme, connecte &lt;em&gt;quatre univers&lt;/em&gt; : la scène hackers américaine (1999-2005), la naissance de la &lt;em&gt;Nerdcore&lt;/em&gt; (hip-hop de nerd, 2005-2008), Digital Gangster (forum 2005-2017), et la culture crypto (dès 2013, avec notamment &lt;em&gt;Bitcoin Baron&lt;/em&gt;). Le Holy Book le cite plusieurs fois.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;1-identité&#34;&gt;1. Identité&lt;a class=&#34;anchor&#34; href=&#34;#1-identit%c3%a9&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Champ&lt;/th&gt;&#xA;          &lt;th&gt;Valeur&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Nom&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Bryce Case Jr.&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Né&lt;/td&gt;&#xA;          &lt;td&gt;23 août 1982, La Mirada, Californie&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Alias&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;YTCracker&lt;/strong&gt; (prononcé « &lt;em&gt;whitey cracker&lt;/em&gt; »)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Statut&lt;/td&gt;&#xA;          &lt;td&gt;Anciennement black hat, désormais white hat (consultant cybersec + musicien)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;Le pseudo « &lt;em&gt;YTCracker&lt;/em&gt; » se prononce &lt;em&gt;whitey cracker&lt;/em&gt;. Il combine « &lt;em&gt;Yours Truly&lt;/em&gt; », le nom d&amp;rsquo;un personnage dans le roman cyberpunk &lt;em&gt;Snow Crash&lt;/em&gt; de Neal Stephenson, et le terme cracker — celui qui s&amp;rsquo;introduit dans des systèmes informatiques. Bryce Case a lui-même expliqué cette étymologie dans plusieurs interviews.&lt;/p&gt;</description>
    </item>
    <item>
      <title>00.04 — Relations entre les six dépôts</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/00-big-picture/04-repo-relations/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/00-big-picture/04-repo-relations/</guid>
      <description>&lt;h1 id=&#34;0004relations-entre-les-six-dépôts&#34;&gt;00.04 — Relations entre les six dépôts&lt;a class=&#34;anchor&#34; href=&#34;#0004relations-entre-les-six-d%c3%a9p%c3%b4ts&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;p&gt;Chaque arête du graphe est un &lt;em&gt;contrat&lt;/em&gt; : un secret, une URL, un type de payload, ou une version. On les détaille ici, par paire.&lt;/p&gt;&#xA;&lt;h2 id=&#34;ocarina--ocarina-example&#34;&gt;&lt;code&gt;ocarina&lt;/code&gt; ↔ &lt;code&gt;ocarina-example&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#ocarina--ocarina-example&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Direction&lt;/th&gt;&#xA;          &lt;th&gt;Mécanisme&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ocarina&lt;/code&gt; → &lt;code&gt;ocarina-example&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;pip install ocarina&lt;/code&gt; depuis PyPI&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ocarina-example&lt;/code&gt; → &lt;code&gt;ocarina&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Aucune (l&amp;rsquo;exemple ne pousse rien)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;L&amp;rsquo;exemple &lt;strong&gt;écrit ses propres adapters&lt;/strong&gt; au-dessus du framework :&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;lib/ext/ocarina/adapters/agnostic/act.py&lt;/code&gt; → ajoute le hook &lt;code&gt;on_failure&lt;/code&gt; qui transforme une page d&amp;rsquo;erreur HTTP (titre matché par &lt;code&gt;ERROR_PAGE_REGEX&lt;/code&gt;) en &lt;code&gt;HttpErrorPageReachedError&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;lib/ext/ocarina/adapters/agnostic/match_page.py&lt;/code&gt; → &lt;code&gt;create_match_page(raised_exceptions=transient_errors)&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;lib/ext/ocarina/adapters/agnostic/env_getters.py&lt;/code&gt; → typed &lt;code&gt;EnvGetters[_CredsKeys, _ValuesKeys]&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;lib/ext/ocarina/adapters/selenium/test_suite.py&lt;/code&gt; → fige &lt;code&gt;max_retries_per_test=8&lt;/code&gt;, &lt;code&gt;transient_errors=...&lt;/code&gt;, &lt;code&gt;autoscreen_on_fail=True&lt;/code&gt;, propage &lt;code&gt;--only&lt;/code&gt;/&lt;code&gt;--exclude&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;lib/ext/ocarina/adapters/selenium/test_campaign.py&lt;/code&gt; → fige &lt;code&gt;max_workers=get_max_workers()&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;ocarina--ocarina-with-ai-example&#34;&gt;&lt;code&gt;ocarina&lt;/code&gt; ↔ &lt;code&gt;ocarina-with-ai-example&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#ocarina--ocarina-with-ai-example&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;pip install . ruff mypy mypy-extensions typing-extensions pre-commit&lt;/code&gt; car l&amp;rsquo;exemple IA a la dépendance fixée dans son &lt;code&gt;pyproject.toml&lt;/code&gt; : &lt;code&gt;ocarina&amp;gt;=1.0.3&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;L&amp;rsquo;adapter &lt;code&gt;act&lt;/code&gt; est &lt;strong&gt;minimaliste&lt;/strong&gt; ici : pas de &lt;code&gt;on_failure&lt;/code&gt; (CURA n&amp;rsquo;a pas de page d&amp;rsquo;erreur volontairement aléatoire à intercepter). Voir &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/08-ai-example/&#34;&gt;&lt;code&gt;../08-ai-example/&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;L&amp;rsquo;adapter &lt;code&gt;create_drivers_pool&lt;/code&gt; est &lt;strong&gt;surchargé&lt;/strong&gt; pour bâtir un Chrome &lt;em&gt;clean&lt;/em&gt; (password manager off, leak detection off). Voir &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/08-ai-example/06-data-gaps/&#34;&gt;&lt;code&gt;../08-ai-example/06-data-gaps.md&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;ocarina-example--igoristan&#34;&gt;&lt;code&gt;ocarina-example&lt;/code&gt; ↔ &lt;code&gt;igoristan&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#ocarina-example--igoristan&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Direction&lt;/th&gt;&#xA;          &lt;th&gt;Contrat&lt;/th&gt;&#xA;          &lt;th&gt;Détail&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ocarina-example&lt;/code&gt; → &lt;code&gt;igoristan&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;URLs publiques&lt;/td&gt;&#xA;          &lt;td&gt;Tout passe par &lt;code&gt;https://mojo-molotov.github.io/igoristan/&amp;lt;route&amp;gt;&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;igoristan&lt;/code&gt; → &lt;code&gt;ocarina-example&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;aucun (le SUT ne sait pas qu&amp;rsquo;il est testé)&lt;/td&gt;&#xA;          &lt;td&gt; — &lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;&lt;code&gt;src/constants/pages/&lt;/code&gt; :&lt;/p&gt;</description>
    </item>
    <item>
      <title>01.04 — Citations et influences revendiquées</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/01-philosophy/04-citations-and-influences/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/01-philosophy/04-citations-and-influences/</guid>
      <description>&lt;h1 id=&#34;0104citations-et-influences-revendiquées&#34;&gt;01.04 — Citations et influences revendiquées&lt;a class=&#34;anchor&#34; href=&#34;#0104citations-et-influences-revendiqu%c3%a9es&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Les références citées dans le Holy Book ne sont pas neutres : elles fixent &lt;strong&gt;le cadre théorique&lt;/strong&gt; d&amp;rsquo;Ocarina. On les recoupe ici par grands axes, puis on en propose une lecture d&amp;rsquo;ensemble.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;axe-techniqueprogrammation-fonctionnelle-types-langages&#34;&gt;Axe technique — Programmation fonctionnelle, types, langages&lt;a class=&#34;anchor&#34; href=&#34;#axe-techniqueprogrammation-fonctionnelle-types-langages&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Influence&lt;/th&gt;&#xA;          &lt;th&gt;Origine&lt;/th&gt;&#xA;          &lt;th&gt;Rapport avec Ocarina&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Railway Oriented Programming (ROP)&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Scott Wlaschin (F#), pattern bien connu en programmation fonctionnelle&lt;/td&gt;&#xA;          &lt;td&gt;Le cœur du framework. &lt;code&gt;Result[T] = Ok[T] | Fail&lt;/code&gt;, &lt;em&gt;short-circuit&lt;/em&gt;, builder fluide. Voir &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/&#34;&gt;&lt;code&gt;../02-ocarina/03-railway/&lt;/code&gt;&lt;/a&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;λ-calcul (1930)&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Alonzo Church&lt;/td&gt;&#xA;          &lt;td&gt;Cité comme l&amp;rsquo;invention qui rend possible la « &lt;em&gt;formalisation propre&lt;/em&gt; » qu&amp;rsquo;Ocarina poursuit : « &lt;em&gt;Ce que l&amp;rsquo;on pensait depuis les années 1930, &lt;strong&gt;depuis l&amp;rsquo;invention du λ-calcul&lt;/strong&gt;, est enfin scalable à l&amp;rsquo;échelle que l&amp;rsquo;on aurait toujours voulu lui donner.&lt;/em&gt; »&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;McCulloch &amp;amp; Pitts (1943)&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Première formalisation du neurone artificiel&lt;/td&gt;&#xA;          &lt;td&gt;Cité comme rappel que les &lt;em&gt;vrais&lt;/em&gt; progrès en IA sont anciens et continus, contre la hype récente.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Système de types Python (PEP 695)&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;PEP 695 (Python 3.12+)&lt;/td&gt;&#xA;          &lt;td&gt;Pré-requis pour le typage générique paramétrique qui structure Ocarina (&lt;code&gt;TestSuite[Driver]&lt;/code&gt;, &lt;code&gt;Result[T]&lt;/code&gt;, etc.). Voir &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/03-functional/06-pep-695-generics/&#34;&gt;&lt;code&gt;../03-functional/06-pep-695-generics.md&lt;/code&gt;&lt;/a&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;axe-culturelanti-narcissisme-simplicité-kiss-bien-compris&#34;&gt;Axe culturel — Anti-narcissisme, simplicité, KISS bien compris&lt;a class=&#34;anchor&#34; href=&#34;#axe-culturelanti-narcissisme-simplicit%c3%a9-kiss-bien-compris&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Influence&lt;/th&gt;&#xA;          &lt;th&gt;Citation&lt;/th&gt;&#xA;          &lt;th&gt;Pourquoi&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Terry Davis&lt;/strong&gt; (« &lt;em&gt;le programmeur le plus intelligent qui ait jamais existé&lt;/em&gt; »)&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;Un idiot admire la complexité, un génie admire la simplicité, un physicien essaie de simplifier…&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;Argumentaire anti-complexité ostentatoire ; pierre angulaire du chapitre « &lt;em&gt;Premiers retours&lt;/em&gt; ».&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Lao-Tseu&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;Pour avoir de la connaissance, ajouter des choses chaque jour. Pour avoir de la sagesse, enlever des choses chaque jour.&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;Démarche de retrait : ce qui reste après élimination.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Antoine de Saint-Exupéry&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;La perfection est atteinte non pas lorsqu&amp;rsquo;il n&amp;rsquo;y a plus rien à ajouter, mais lorsqu&amp;rsquo;il n&amp;rsquo;y a plus rien à retirer.&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;Reformulation occidentale du précédent. Clos le chapitre « &lt;em&gt;Premiers scénarios&lt;/em&gt; ».&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Alan Watts&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;La meilleure façon de résoudre un problème est souvent d&amp;rsquo;en sortir.&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;Cité en fin du chapitre « &lt;em&gt;Premiers jutsus&lt;/em&gt; » : légitime un design qui &lt;em&gt;refuse&lt;/em&gt; certaines fonctionnalités (réactivité, async). Légitime la sortie de l&amp;rsquo;écosystème des &lt;em&gt;trucs de geeks&lt;/em&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Rainer Maria Rilke&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;Pour l&amp;rsquo;instant, vivez les questions…&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;Clos le chapitre « &lt;em&gt;Premiers pas&lt;/em&gt; ». Posture envers la pratique : la maîtrise vient par l&amp;rsquo;usage.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Marcel Proust&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;Le style (…) est une question non de technique, mais de vision.&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;Clos le chapitre « &lt;em&gt;Extensibilité&lt;/em&gt; ». Justifie le refus d&amp;rsquo;imposer un DSL : la vision (POMs + actions) prime sur la technique (un format).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;axe-politiquecode-souveraineté-anti-startup-nation&#34;&gt;Axe politique — Code, souveraineté, anti-startup-nation&lt;a class=&#34;anchor&#34; href=&#34;#axe-politiquecode-souverainet%c3%a9-anti-startup-nation&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Influence&lt;/th&gt;&#xA;          &lt;th&gt;Origine&lt;/th&gt;&#xA;          &lt;th&gt;Rapport avec Ocarina&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;em&gt;Code is Law&lt;/em&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Lawrence Lessig (1999), repris par Ethereum (2015) comme idéal positif&lt;/td&gt;&#xA;          &lt;td&gt;Cité comme la posture face à la « &lt;em&gt;gouvernance démocratique du code&lt;/em&gt; ». Le code est la donnée brute, auditable. Le projet revendique : « &lt;em&gt;De nouveau, avec l&amp;rsquo;IA, ce maillon qu&amp;rsquo;il manquait cruellement, crions-le, aussi fort que l&amp;rsquo;on criait &amp;ldquo;HACK THE PLANET&amp;rdquo; en 99 : &lt;strong&gt;CODE IS LAW.&lt;/strong&gt;&lt;/em&gt; »&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;DHH — &lt;em&gt;I won&amp;rsquo;t let you pay me for my open source&lt;/em&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;David Heinemeier Hansson, blog perso&lt;/td&gt;&#xA;          &lt;td&gt;Cité pour légitimer le refus de contributions non alignées (« &lt;em&gt;Fuck You&lt;/em&gt; » fait référence à la célèbre slide de DHH.)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Paul Graham — &lt;em&gt;Haters&lt;/em&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Essai PG (&lt;code&gt;paulgraham.com/fh.html&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;Justifie la décision de « &lt;em&gt;ne JAMAIS perdre [son] temps à argumenter lorsque ce n&amp;rsquo;est pas la peine&lt;/em&gt; ».&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Yegor Bugayenko — &lt;em&gt;Prompt&lt;/em&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Citation « &lt;em&gt;Retry flaky blocks.&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;Fond la politique &lt;code&gt;transient_errors&lt;/code&gt; + retries linéaires (&lt;code&gt;TestFlow&lt;/code&gt;). Lire aussi : &lt;em&gt;Angry Tests&lt;/em&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;The DAO hack (2016)&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Hack de The DAO sur Ethereum&lt;/td&gt;&#xA;          &lt;td&gt;Cité pour pointer que ce sont &lt;em&gt;les bugs&lt;/em&gt; (« &lt;em&gt;des satanés bugs, à cause de satanés dévs&lt;/em&gt; ») qui ont fait reculer l&amp;rsquo;idée de &lt;em&gt;Code is Law&lt;/em&gt;, d&amp;rsquo;où l&amp;rsquo;importance du typage et de la rigueur.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Lee Robinson (ex-Vercel)&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Décembre 2025 : remplacement de Sanity par des fichiers Markdown + IA chez Cursor&lt;/td&gt;&#xA;          &lt;td&gt;Soutient le « &lt;em&gt;retour à la donnée brute&lt;/em&gt; ».&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Cluely (Roy Lee)&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Cité pour illustrer le &lt;em&gt;bullshit&lt;/em&gt; entrepreneurial à éviter&lt;/td&gt;&#xA;          &lt;td&gt;Contraste explicite avec la philosophie Ocarina.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;axe-identitaire-underground&#34;&gt;Axe identitaire / &lt;em&gt;underground&lt;/em&gt;&lt;a class=&#34;anchor&#34; href=&#34;#axe-identitaire-underground&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Influence&lt;/th&gt;&#xA;          &lt;th&gt;Origine&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Lulzsec&lt;/strong&gt; — « &lt;em&gt;In the Lulzboat, salute, bitch, and show some respect.&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;Antisec (YTCracker, 2011)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;« &lt;em&gt;We Can Do All What You Can&amp;rsquo;t Do&lt;/em&gt; »&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;YogyaCarderLink&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;YTCracker — &lt;em&gt;Robots Will Definitely Take Your Job&lt;/em&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Lien SoundCloud, musique de fond du chapitre « &lt;em&gt;Premiers retours&lt;/em&gt; »&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;« &lt;em&gt;HACK THE PLANET&lt;/em&gt; » (1999)&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Slogan emblématique de la scène hackers des années 1990 + Antisec (YTCracker, 2011)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Zone-H&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Site historique de defaces web&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;r/unixporn&lt;/strong&gt;, &lt;strong&gt;r/AntiTaff&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Subreddits&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;axe-ia-outillage-moderne&#34;&gt;Axe IA / outillage moderne&lt;a class=&#34;anchor&#34; href=&#34;#axe-ia-outillage-moderne&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Influence&lt;/th&gt;&#xA;          &lt;th&gt;Origine&lt;/th&gt;&#xA;          &lt;th&gt;Rapport avec Ocarina&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Claude Code&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Anthropic&lt;/td&gt;&#xA;          &lt;td&gt;Outillage de référence du projet IA (&lt;code&gt;ocarina-with-ai-example&lt;/code&gt;). Cité plusieurs fois comme « &lt;em&gt;pont&lt;/em&gt; » remplaçant les DSLs.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;IntelliCode (2018)&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Microsoft&lt;/td&gt;&#xA;          &lt;td&gt;Cité comme repère temporel (« &lt;em&gt;on y avait déjà goûté, avant même IntelliCode&lt;/em&gt; »).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;R., savant fou du silo&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Anecdote personnelle (un collègue de 2013, plugin Emacs IA privé)&lt;/td&gt;&#xA;          &lt;td&gt;Rappel que l&amp;rsquo;IA générative (« &lt;em&gt;15 000 lignes à supprimer pour éviter d&amp;rsquo;en écrire 1000&lt;/em&gt; ») existait avant la hype publique.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Prisma, Vercel, Cursor&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Écosystème SaaS&lt;/td&gt;&#xA;          &lt;td&gt;Cités comme cas pratiques de &lt;em&gt;retour à la donnée brute&lt;/em&gt; assisté par IA.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;cohérence-densemble&#34;&gt;Cohérence d&amp;rsquo;ensemble&lt;a class=&#34;anchor&#34; href=&#34;#coh%c3%a9rence-densemble&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Toutes ces influences pointent vers &lt;strong&gt;trois axes convergents&lt;/strong&gt; :&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.03.04 — chain_actions et le ChainRunner</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/04-chain-actions-fold/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/04-chain-actions-fold/</guid>
      <description>&lt;h1 id=&#34;020304chain_actions-et-le-chainrunner&#34;&gt;02.03.04 — &lt;code&gt;chain_actions&lt;/code&gt; et le &lt;code&gt;ChainRunner&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#020304chain_actions-et-le-chainrunner&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing_with_railway/chain_actions.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/testing_with_railway/chain_actions.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;C&amp;rsquo;est la primitive qui rend le DSL « &lt;em&gt;plat&lt;/em&gt; ». Sans elle, un scénario à N pas serait un escalier de &lt;code&gt;.then(...).failure(...).success(...).execute()&lt;/code&gt;. Avec elle, c&amp;rsquo;est une liste.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;parenthesis-hell&#34;&gt;« &lt;em&gt;Parenthesis hell&lt;/em&gt; »&lt;a class=&#34;anchor&#34; href=&#34;#parenthesis-hell&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Solution :&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# Flat, readable syntax&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;runner &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; chain_actions(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    action1&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;failure(h1)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;success(h2),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    action2&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;failure(h3)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;success(h4),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    action3&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;failure(h5)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;success(h6)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;chain &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; runner&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;run()  &lt;span style=&#34;color:#6272a4&#34;&gt;# Execute when ready&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Benefits :&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Lazy evaluation&lt;/strong&gt; : Build chain without executing&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Flat syntax&lt;/strong&gt; : No deep nesting&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Automatic short-circuiting&lt;/strong&gt; : Stops on first failure&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Composability&lt;/strong&gt; : ChainRunner is a value&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/blockquote&gt;&lt;p&gt;&lt;em&gt;(Extrait de docstring.)&lt;/em&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.04.04 — .then(...) et chain_validations(...)</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/04-invariants/04-then-chain-of-validations/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/04-invariants/04-then-chain-of-validations/</guid>
      <description>&lt;h1 id=&#34;020404then-et-chain_validations&#34;&gt;02.04.04 — &lt;code&gt;.then(...)&lt;/code&gt; et &lt;code&gt;chain_validations(...)&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#020404then-et-chain_validations&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Deux mécanismes différents pour composer plusieurs validations en une seule.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;thennew_value&#34;&gt;&lt;code&gt;.then(new_value)&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#thennew_value&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;then&lt;/span&gt;[U](&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, new_value: U, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, name: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; ValidationStartBlock[U]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; ValidationStartBlock(new_value, &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_chain, name)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Change le type&lt;/strong&gt; de &lt;code&gt;T&lt;/code&gt; à &lt;code&gt;U&lt;/code&gt;. Le checker accepte le passage à un autre type.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Conserve la chaîne&lt;/strong&gt; (&lt;code&gt;self._chain&lt;/code&gt;). Toutes les assertions précédentes restent dans la chaîne ; toutes les suivantes &lt;em&gt;s&amp;rsquo;ajoutent&lt;/em&gt; à la chaîne.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Bascule le &lt;code&gt;name&lt;/code&gt;&lt;/strong&gt; : c&amp;rsquo;est le &lt;code&gt;name&lt;/code&gt; fourni à &lt;code&gt;.then()&lt;/code&gt; qui sera utilisé pour les assertions ajoutées ensuite (et non celui du block précédent).&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h3 id=&#34;cas-dusage-canonique&#34;&gt;Cas d&amp;rsquo;usage canonique&lt;a class=&#34;anchor&#34; href=&#34;#cas-dusage-canonique&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;validate(user, name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;user&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;assert_that(is_not_none)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;then(user&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;email, name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;user.email&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;assert_that(is_email)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;then(user&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;age, name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;user.age&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;assert_that(is_positive)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;assert_that(is_less_than_or_equal_to(&lt;span style=&#34;color:#bd93f9&#34;&gt;120&lt;/span&gt;))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;execute()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;raise_if_invalid()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Lecture : on valide &lt;code&gt;user&lt;/code&gt; puis &lt;code&gt;user.email&lt;/code&gt; puis &lt;code&gt;user.age&lt;/code&gt;. Si &lt;em&gt;toutes&lt;/em&gt; ces validations passent : OK. Si ne serait-ce qu&amp;rsquo;&lt;em&gt;une seule&lt;/em&gt; échoue, toutes les erreurs sont agrégées dans le &lt;code&gt;AggregateInvariantViolationError&lt;/code&gt;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.05.04 — TestSuite[Driver] — moteur parallélisé</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/05-orchestration/04-test-suite/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/05-orchestration/04-test-suite/</guid>
      <description>&lt;h1 id=&#34;020504testsuitedrivermoteur-parallélisé&#34;&gt;02.05.04 — &lt;code&gt;TestSuite[Driver]&lt;/code&gt; — moteur parallélisé&lt;a class=&#34;anchor&#34; href=&#34;#020504testsuitedrivermoteur-parall%c3%a9lis%c3%a9&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing/oc_test_suite.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/testing/oc_test_suite.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Responsabilité unique&lt;/strong&gt; : exécuter une séquence de &lt;code&gt;Test&lt;/code&gt; en parallèle, gérer la &lt;em&gt;saturation&lt;/em&gt;, le filtrage par IDs, et la validation des invariants pré-exécution.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;constructeur&#34;&gt;Constructeur&lt;a class=&#34;anchor&#34; href=&#34;#constructeur&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;__init__&lt;/span&gt;(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    name: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tests: Sequence[Test[Driver]],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    create_logger: Thunk[ILogger],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    drivers_pool: WebDriversPool[Driver],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    take_screenshot: ITakeScreenshot[Driver],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    act_counter: ActCounter &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    transient_errors: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;tuple&lt;/span&gt;[&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt;[Exception], &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; (),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    copy_indicator: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;COPY&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    put_space_after_copy_indicator: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;True&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    max_retries_per_test: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    autoscreen_on_fail: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;False&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    saturate_workers: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    only_ids: Iterable[&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; (),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    exclude_ids: Iterable[&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; (),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_guards_on_invoke(tests)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#6272a4&#34;&gt;# ... assignations ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_tests &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; filter_tests_by_ids(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        tests,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        only&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;only_ids,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        exclude&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;exclude_ids,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        logger&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;create_logger()&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;set_prefix(&lt;span style=&#34;color:#ff79c6&#34;&gt;lambda&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;: filtering tests...&amp;#34;&lt;/span&gt;),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Paramètre&lt;/th&gt;&#xA;          &lt;th&gt;Type&lt;/th&gt;&#xA;          &lt;th&gt;Rôle&lt;/th&gt;&#xA;          &lt;th&gt;Défaut&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Nom de la suite (apparaît dans logs &amp;amp; rapports)&lt;/td&gt;&#xA;          &lt;td&gt; — &lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;tests&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Sequence[Test[Driver]]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Liste des tests à exécuter&lt;/td&gt;&#xA;          &lt;td&gt; — &lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;create_logger&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Thunk[ILogger]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Factory de logger&lt;/td&gt;&#xA;          &lt;td&gt; — &lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;drivers_pool&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;WebDriversPool[Driver]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Pool partagée&lt;/td&gt;&#xA;          &lt;td&gt; — &lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;take_screenshot&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ITakeScreenshot[Driver]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Callable &lt;code&gt;(driver, logger, category) -&amp;gt; None&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt; — &lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;act_counter&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ActCounter | None&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Si &lt;code&gt;None&lt;/code&gt; → &lt;code&gt;ThreadsBasedActCounter()&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;transient_errors&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;tuple[type[Exception], ...]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Exceptions qui déclenchent un retry&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;()&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;copy_indicator&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Préfixe pour les tests clonés en saturation&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;&amp;quot;COPY&amp;quot;&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;put_space_after_copy_indicator&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;[COPY 1]&lt;/code&gt; (&lt;code&gt;True&lt;/code&gt;) vs &lt;code&gt;[COPY1]&lt;/code&gt; (&lt;code&gt;False&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;max_retries_per_test&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;int | None&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Nombre maximum de retentatives d&amp;rsquo;un test potentiellement flaky&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;None&lt;/code&gt; → &lt;code&gt;_DEFAULT_MAX_RETRIES_PER_TEST&lt;/code&gt; (8)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;autoscreen_on_fail&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Capture automatiquement un ou des screenshots (rafale) en cas d&amp;rsquo;échec&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;saturate_workers&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;bool | None&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Clonage des tests pour saturer tous les workers si la pool est suffisamment grosse&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;None&lt;/code&gt; → cascade : suite &amp;gt; campaign &amp;gt; bootstrap&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;only_ids&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Iterable[str]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Filtre &lt;code&gt;--only&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;()&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;exclude_ids&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Iterable[str]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Filtre &lt;code&gt;--exclude&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;()&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;garde-fous&#34;&gt;Garde-fous&lt;a class=&#34;anchor&#34; href=&#34;#garde-fous&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;au-moment-du-__init__&#34;&gt;Au moment du &lt;code&gt;__init__&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#au-moment-du-__init__&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_guards_on_invoke&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, tests: Sequence[Test[Driver]]) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    validate_test_runners_ids(tests&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;tests, name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;tests&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;execute()&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;raise_if_invalid()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;→ Tous les &lt;code&gt;test_id&lt;/code&gt; doivent être uniques. Sinon : &lt;code&gt;AggregateInvariantViolationError&lt;/code&gt;. &lt;em&gt;Levé dès la construction&lt;/em&gt;, avant la moindre tentative d&amp;rsquo;exécution. Si l&amp;rsquo;utilisateur a dupliqué un &lt;code&gt;test_id&lt;/code&gt; par erreur, il l&amp;rsquo;apprend immédiatement.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.10.04 — ActCounter &#43; ThreadsBasedActCounter</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/04-act-counter/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/04-act-counter/</guid>
      <description>&lt;h1 id=&#34;021004actcounter--threadsbasedactcounter&#34;&gt;02.10.04 — &lt;code&gt;ActCounter&lt;/code&gt; + &lt;code&gt;ThreadsBasedActCounter&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#021004actcounter--threadsbasedactcounter&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Compteur thread-local du nombre d&amp;rsquo;&lt;code&gt;act&lt;/code&gt; exécutés par tentative. Permet de reporter au rapport « &lt;em&gt;ce test a fait 17 steps avant d&amp;rsquo;échouer au step 18&lt;/em&gt; ».&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;interface&#34;&gt;Interface&lt;a class=&#34;anchor&#34; href=&#34;#interface&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/ocarina/infra/act_counter.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;ActCounter&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;reset&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;incr_act_call_count&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;implémentation-par-défaut&#34;&gt;Implémentation par défaut&lt;a class=&#34;anchor&#34; href=&#34;#impl%c3%a9mentation-par-d%c3%a9faut&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/ocarina/opinionated/infra/act_counter.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; threading &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; local&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;_thread_local &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; local()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;_COUNTER_KEY: Final[&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ocarina_counter&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;ActCounter&lt;/span&gt;(_ActCounter):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;getattr&lt;/span&gt;(_thread_local, _COUNTER_KEY, &lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;reset&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;setattr&lt;/span&gt;(_thread_local, _COUNTER_KEY, &lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;incr_act_call_count&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;hasattr&lt;/span&gt;(_thread_local, _COUNTER_KEY):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;reset()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;setattr&lt;/span&gt;(_thread_local, _COUNTER_KEY, &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;getattr&lt;/span&gt;(_thread_local, _COUNTER_KEY) &lt;span style=&#34;color:#ff79c6&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;threading.local()&lt;/code&gt;&lt;/strong&gt; : un namespace dont les attributs sont &lt;em&gt;par thread&lt;/em&gt;. Chaque thread a sa propre valeur.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;_COUNTER_KEY = &amp;quot;ocarina_counter&amp;quot;&lt;/code&gt;&lt;/strong&gt; : la clé d&amp;rsquo;attribut.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Pas de lock&lt;/strong&gt; : chaque thread n&amp;rsquo;écrit que sa propre valeur, donc pas de race condition.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;pourquoi-thread-local-plutôt-quun-compteur-partagé--lock&#34;&gt;Pourquoi thread-local plutôt qu&amp;rsquo;un compteur partagé + lock ?&lt;a class=&#34;anchor&#34; href=&#34;#pourquoi-thread-local-plut%c3%b4t-quun-compteur-partag%c3%a9--lock&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Ocarina est architecturé de sorte qu&amp;rsquo;&lt;strong&gt;un thread = un test&lt;/strong&gt;. Et aucune envie d&amp;rsquo;introduire toute une &lt;em&gt;state monad&lt;/em&gt; ou quoi, autant placer cette « &lt;em&gt;impureté&lt;/em&gt; » (qui reste un &amp;ldquo;état&amp;rdquo;) ici. C&amp;rsquo;est tout.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.11.04 — Loggers : PrintLogger, FileLogger, PrintAndFileLogger, MutedLogger</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/11-opinionated/04-loggers/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/11-opinionated/04-loggers/</guid>
      <description>&lt;h1 id=&#34;021104loggers-printlogger-filelogger-printandfilelogger-mutedlogger&#34;&gt;02.11.04 — Loggers : &lt;code&gt;PrintLogger&lt;/code&gt;, &lt;code&gt;FileLogger&lt;/code&gt;, &lt;code&gt;PrintAndFileLogger&lt;/code&gt;, &lt;code&gt;MutedLogger&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#021104loggers-printlogger-filelogger-printandfilelogger-mutedlogger&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Dossier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/tree/main/src/ocarina/opinionated/loggers&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/opinionated/loggers/&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Quatre implémentations canoniques d&amp;rsquo;&lt;code&gt;ILogger&lt;/code&gt;, plus une factory &lt;code&gt;create_matching_logger&lt;/code&gt;. Toutes opt-in.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;create_matching_logger&#34;&gt;&lt;code&gt;create_matching_logger&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#create_matching_logger&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;LOGGERS_CHOICES &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;terminal&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;file&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;terminal+file&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;muted&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;create_matching_logger&lt;/span&gt;(mode: SupportedLogger) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; ILogger:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; mode &lt;span style=&#34;color:#ff79c6&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;terminal&amp;#34;&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; PrintLogger()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; mode &lt;span style=&#34;color:#ff79c6&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;file&amp;#34;&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; FileLogger(base_dir&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;get_default_log_dir())&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; mode &lt;span style=&#34;color:#ff79c6&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;terminal+file&amp;#34;&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; PrintAndFileLogger(base_dir&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;get_default_log_dir())&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; mode &lt;span style=&#34;color:#ff79c6&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;muted&amp;#34;&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; MutedLogger()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;raise&lt;/span&gt; ValueError(&lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Unsupported logger mode: &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;mode&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Et :&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;get_default_log_dir&lt;/span&gt;() &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; Path:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; Path&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;cwd() &lt;span style=&#34;color:#ff79c6&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;.ocarina_logs&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;→ Par défaut, les logs vont dans &lt;code&gt;.ocarina_logs/&lt;/code&gt; à la racine du projet utilisateur. Gitignored par convention.&lt;/p&gt;</description>
    </item>
    <item>
      <title>03.04 — reduce / fold dans Ocarina</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/03-functional/04-fold-reduce/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/03-functional/04-fold-reduce/</guid>
      <description>&lt;h1 id=&#34;0304reduce-fold-dans-ocarina&#34;&gt;03.04 — &lt;code&gt;reduce&lt;/code&gt; / fold dans Ocarina&lt;a class=&#34;anchor&#34; href=&#34;#0304reduce-fold-dans-ocarina&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Un seul &lt;code&gt;reduce&lt;/code&gt; dans toute la base de code. Mais c&amp;rsquo;est &lt;strong&gt;le&lt;/strong&gt; réducteur central : il fait fonctionner &lt;code&gt;chain_actions&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;code&#34;&gt;Code&lt;a class=&#34;anchor&#34; href=&#34;#code&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/ocarina/dsl/testing_with_railway/chain_actions.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; functools &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; reduce&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;chain_actions&lt;/span&gt;[T](&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    first: ActionSuccess[T], &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;rest: ActionSuccess[T]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; ChainRunner[T]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;thunk&lt;/span&gt;() &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; ActionChain[T]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;reducer&lt;/span&gt;(chain: ActionChain[T], step: ActionSuccess[T]) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; ActionChain[T]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; chain&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;has_failed():&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; chain&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; (&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                chain&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;then(step&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;__action__)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;failure(step&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;__failure_handler__)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;success(step&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;__success_handler__)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;execute()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; reduce(reducer, rest, first&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;execute())&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; ChainRunner(thunk&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;thunk)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;anatomie&#34;&gt;Anatomie&lt;a class=&#34;anchor&#34; href=&#34;#anatomie&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Concept FP&lt;/th&gt;&#xA;          &lt;th&gt;Réalisation Python&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Type accumulator&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ActionChain[T]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Type element&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ActionSuccess[T]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Reducer&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;reducer(chain, step) -&amp;gt; ActionChain[T]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Initial value&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;first.execute()&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Iterable&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;rest&lt;/code&gt; (le tuple de &lt;code&gt;*rest&lt;/code&gt;)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Short-circuit&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;if chain.has_failed(): return chain&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Result&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ActionChain[T]&lt;/code&gt; (le dernier)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;flot-dexécution&#34;&gt;Flot d&amp;rsquo;exécution&lt;a class=&#34;anchor&#34; href=&#34;#flot-dex%c3%a9cution&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;initial = first.execute()                                          # → ActionChain(ok=True, result=Ok(...))&#xA;&#xA;step 1 : reducer(&amp;lt;initial&amp;gt;, act2) :&#xA;  chain.has_failed() = False&#xA;  chain.then(act2.__action__).failure(...).success(...).execute()&#xA;  → action lève → Fail&#xA;  → failure_handler(exc)  ❌ FIRE&#xA;  → ActionChain(ok=False, result=Fail(exc))&#xA;&#xA;step 2 : reducer(&amp;lt;failed&amp;gt;, act3) :&#xA;  chain.has_failed() = True&#xA;  return chain                                                     ⚠️  short-circuit&#xA;&#xA;result du reduce = &amp;lt;failed ActionChain&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;pourquoi-reduce&#34;&gt;Pourquoi &lt;code&gt;reduce&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#pourquoi-reduce&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# Approche impérative (équivalente)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;thunk&lt;/span&gt;() &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; ActionChain[T]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    chain &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; first&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;execute()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; step &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; rest:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; chain&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;has_failed():&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;break&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        chain &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; (&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            chain&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;then(step&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;__action__)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;failure(step&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;__failure_handler__)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;success(step&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;__success_handler__)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;execute()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; chain&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Reduce&lt;/th&gt;&#xA;          &lt;th&gt;Boucle impérative&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Une seule &lt;strong&gt;valeur&lt;/strong&gt; circule (le &lt;code&gt;chain&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;Un état + une mutation&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Reducer = fonction pure (testable en isolation)&lt;/td&gt;&#xA;          &lt;td&gt;Logique inline&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Sémantique « &lt;em&gt;accumulateur&lt;/em&gt; » explicite&lt;/td&gt;&#xA;          &lt;td&gt;Logique implicite&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Standard fonctionnel&lt;/td&gt;&#xA;          &lt;td&gt;Standard impératif&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;C&amp;rsquo;est une &lt;strong&gt;expression d&amp;rsquo;intention&lt;/strong&gt; : on &lt;em&gt;replie&lt;/em&gt; une liste sur un accumulator. La sémantique est claire dès la première lecture. C&amp;rsquo;est ce qu&amp;rsquo;on appelle un &lt;strong&gt;catamorphisme&lt;/strong&gt;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>04.04 — Tests du typage statique (pytest-mypy-plugins)</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/04-mypy-plugins-types/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/04-mypy-plugins-types/</guid>
      <description>&lt;h1 id=&#34;0404tests-du-typage-statique-pytest-mypy-plugins&#34;&gt;04.04 — Tests du &lt;em&gt;typage statique&lt;/em&gt; (&lt;code&gt;pytest-mypy-plugins&lt;/code&gt;)&lt;a class=&#34;anchor&#34; href=&#34;#0404tests-du-typage-statique-pytest-mypy-plugins&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Cinq fichiers &lt;code&gt;*test_types.yml&lt;/code&gt; qui testent &lt;em&gt;le comportement du checker&lt;/em&gt;. C&amp;rsquo;est une dimension de tests &lt;strong&gt;unique&lt;/strong&gt; à Ocarina dans son genre : on s&amp;rsquo;assure que ce qui &lt;em&gt;devrait&lt;/em&gt; être une erreur &lt;em&gt;mypy&lt;/em&gt; en est bien une.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;listing&#34;&gt;Listing&lt;a class=&#34;anchor&#34; href=&#34;#listing&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;tests/&#xA;├── dsl/&#xA;│   ├── invariants/test_types.yml&#xA;│   └── testing_with_railway/test_types.yml&#xA;└── opinionated/&#xA;    ├── cli/test_types.yml&#xA;    ├── infra/test_types.yml&#xA;    └── plugins/reports/...   (snapshots — pas YAML)&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;yaml&#34;&gt;YAML&lt;a class=&#34;anchor&#34; href=&#34;#yaml&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- &lt;span style=&#34;color:#ff79c6&#34;&gt;case&lt;/span&gt;: compatible_predicate_types&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: Predicates should accept values of compatible types&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;main&lt;/span&gt;: |&lt;span style=&#34;color:#f1fa8c&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;    from ocarina.dsl.invariants.validate import validate&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;    from ocarina.dsl.invariants.assertions import is_email, is_positive&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;    validate(1234).assert_that(is_positive)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;    validate(&amp;#34;a@a.com&amp;#34;).assert_that(is_email)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Sans &lt;code&gt;out:&lt;/code&gt; → on attend que &lt;code&gt;mypy&lt;/code&gt; passe &lt;strong&gt;sans erreur&lt;/strong&gt; sur le code donné.&lt;/p&gt;</description>
    </item>
    <item>
      <title>05.04 — Composants UI clés</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/05-igoristan/04-key-components/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/05-igoristan/04-key-components/</guid>
      <description>&lt;h1 id=&#34;0504composants-ui-clés&#34;&gt;05.04 — Composants UI clés&lt;a class=&#34;anchor&#34; href=&#34;#0504composants-ui-cl%c3%a9s&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Les composants &lt;code&gt;src/components/&lt;/code&gt; et leur rôle dans le terrain de jeu.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;listing&#34;&gt;Listing&lt;a class=&#34;anchor&#34; href=&#34;#listing&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;src/components/&#xA;├── BackToHome.tsx              # bouton de retour à HOME&#xA;├── BackToSicily.tsx            # variante&#xA;├── Button.tsx                  # button stylisé Tailwind&#xA;├── ChaoticForm.tsx             # ⭐ formulaire avec éléments .catch-me-if-you-can&#xA;├── Dropzone.tsx                # ⭐ react-dropzone wrapper pour SACRED_UPLOAD&#xA;├── ErrorCode.tsx               # composant affichant un code HTTP (utilisé par RANDOM_ERROR)&#xA;├── FakeUpload/                 # composant fake d&amp;#39;upload (animation, pas de vrai POST)&#xA;├── InternalErrorMsg.tsx        # message d&amp;#39;erreur générique&#xA;├── Label.tsx                   # label de formulaire&#xA;├── Link.tsx                    # wrapper de &amp;lt;a&amp;gt; avec styles&#xA;├── Loading.tsx                 # spinner générique&#xA;├── LoginForm.tsx               # ⭐ formulaire login + MFA OTP&#xA;├── PlusButton.tsx              # bouton + (corsicamon : ajouter à la collection)&#xA;├── RandomBibleVerse.tsx        # verset biblique aléatoire&#xA;├── RandomLoader.tsx            # ⭐ loader random&#xA;├── UploadPreview.tsx           # preview d&amp;#39;un fichier uploadé&#xA;└── XButton.tsx                 # bouton X de fermeture&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;⭐ = composant clé pour les scénarios Ocarina.&lt;/p&gt;</description>
    </item>
    <item>
      <title>06.04 — GET /api/corsicadex?id=N</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/06-tests-workers/04-corsicadex/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/06-tests-workers/04-corsicadex/</guid>
      <description>&lt;h1 id=&#34;0604get-apicorsicadexidn&#34;&gt;06.04 — &lt;code&gt;GET /api/corsicadex?id=N&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0604get-apicorsicadexidn&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/tests-workers/blob/main/api/corsicadex.ts&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;api/corsicadex.ts&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;code&#34;&gt;Code&lt;a class=&#34;anchor&#34; href=&#34;#code&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;type&lt;/span&gt; { NextRequest } &lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;next/server&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; isAuthorized &lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;../lib/isAuthorized&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; { corsicaDexData } &lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;../consts/corsicadexData&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; config &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; { runtime&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;edge&amp;#34;&lt;/span&gt; };&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; corsHeaders &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Access-Control-Allow-Origin&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;*&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Access-Control-Allow-Methods&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;GET, OPTIONS&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Access-Control-Allow-Headers&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;x-api-key, Content-Type&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#ff79c6&#34;&gt;as&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;default&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;function&lt;/span&gt; handler(request: &lt;span style=&#34;color:#8be9fd&#34;&gt;NextRequest&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; (request.method &lt;span style=&#34;color:#ff79c6&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;OPTIONS&amp;#34;&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;new&lt;/span&gt; Response(&lt;span style=&#34;color:#ff79c6&#34;&gt;null&lt;/span&gt;, { status: &lt;span style=&#34;color:#8be9fd&#34;&gt;200&lt;/span&gt;, headers: &lt;span style=&#34;color:#8be9fd&#34;&gt;corsHeaders&lt;/span&gt; });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#ff79c6&#34;&gt;!&lt;/span&gt;isAuthorized(request)) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;new&lt;/span&gt; Response(JSON.stringify({ error&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Unauthorized&amp;#34;&lt;/span&gt; }), {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      status: &lt;span style=&#34;color:#8be9fd&#34;&gt;401&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      headers&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; { &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;, ...corsHeaders },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; url &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;new&lt;/span&gt; URL(request.url);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; id &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; url.searchParams.&lt;span style=&#34;color:#ff79c6&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;id&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#ff79c6&#34;&gt;!&lt;/span&gt;id) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;new&lt;/span&gt; Response(JSON.stringify({ error&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Missing id parameter&amp;#34;&lt;/span&gt; }), {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      status: &lt;span style=&#34;color:#8be9fd&#34;&gt;400&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      headers&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; { &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;, ...corsHeaders },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; corsicamonId &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;parseInt&lt;/span&gt;(id, &lt;span style=&#34;color:#bd93f9&#34;&gt;10&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;isNaN&lt;/span&gt;(corsicamonId)) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;new&lt;/span&gt; Response(JSON.stringify({ error&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Invalid ID&amp;#34;&lt;/span&gt; }), {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      status: &lt;span style=&#34;color:#8be9fd&#34;&gt;400&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      headers&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; { &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;, ...corsHeaders },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; corsicamon &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; corsicaDexData.find((p) &lt;span style=&#34;color:#ff79c6&#34;&gt;=&amp;gt;&lt;/span&gt; p.id &lt;span style=&#34;color:#ff79c6&#34;&gt;===&lt;/span&gt; corsicamonId);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#ff79c6&#34;&gt;!&lt;/span&gt;corsicamon) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;new&lt;/span&gt; Response(JSON.stringify({ error&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Corsicamon not found&amp;#34;&lt;/span&gt; }), {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      status: &lt;span style=&#34;color:#8be9fd&#34;&gt;404&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      headers&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; { &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;, ...corsHeaders },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;new&lt;/span&gt; Response(JSON.stringify(corsicamon), {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    headers&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; { &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;, ...corsHeaders },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;mécanique&#34;&gt;Mécanique&lt;a class=&#34;anchor&#34; href=&#34;#m%c3%a9canique&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;em&gt;Lookup&lt;/em&gt; statique dans un &lt;em&gt;array&lt;/em&gt; TypeScript.&lt;/p&gt;</description>
    </item>
    <item>
      <title>07.04 — Scénarios Corsicamon</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/04-scenarios-corsicamon/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/04-scenarios-corsicamon/</guid>
      <description>&lt;h1 id=&#34;0704scénarios-corsicamon&#34;&gt;07.04 — Scénarios Corsicamon&lt;a class=&#34;anchor&#34; href=&#34;#0704sc%c3%a9narios-corsicamon&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;La page « &lt;em&gt;Pokédex corse (Corsicadex)&lt;/em&gt; » de l&amp;rsquo;Igoristan. Saisie d&amp;rsquo;API key, tirage au sort avec 1/5 d&amp;rsquo;échec, &lt;code&gt;transient_errors&lt;/code&gt; pour &lt;code&gt;Error(&#39;lol&#39;)&lt;/code&gt;, et retries internes au POM face aux états « &lt;em&gt;Corsicamons chargés / erreur réseau&lt;/em&gt; ».&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;campagne&#34;&gt;Campagne&lt;a class=&#34;anchor&#34; href=&#34;#campagne&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/tests/campaigns/corsicamon.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;create_igoristan_corsicamon_campaign&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, drivers_pool) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; TestCampaign:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; TestCampaign(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Corsicamon&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        suites&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;[&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            create_corsicamon_happy_paths_test_suite(drivers_pool&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;drivers_pool),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            create_corsicamon_unhappy_paths_test_suite(drivers_pool&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;drivers_pool),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;create_igoristan_corsicamon_smoke_campaign&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, drivers_pool) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; TestCampaign:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; TestCampaign(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Corsicamon - Smoke&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        suites&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;[create_corsicamon_smoke_tests_suite(drivers_pool&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;drivers_pool)],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;smoke-test&#34;&gt;Smoke test&lt;a class=&#34;anchor&#34; href=&#34;#smoke-test&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# tests/scenarios/corsicamon/enter_api_key.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;scenario_enter_api_key_smoke&lt;/span&gt;(driver, logger):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    page &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; CorsicamonEnterApiKeyPage(driver&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;driver)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; [&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        drive_page(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(page, open_corsicamon_page)&lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(page, verify_enter_api_key_screen)&lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;test_enter_api_key_smoke &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; create_selenium_test(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Corsicamon - Smoke - enter API key screen visible&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    test_scenario&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;lambda&lt;/span&gt; driver, logger: Scenario(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        test_chain&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;scenario_enter_api_key_smoke(driver, logger)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Vérifie juste que la page d&amp;rsquo;entrée d&amp;rsquo;API key s&amp;rsquo;affiche. Si elle ne s&amp;rsquo;affiche pas → smoke fail → main campaigns skippées.&lt;/p&gt;</description>
    </item>
    <item>
      <title>08.04 — Stratégie de test</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/08-ai-example/04-test-strategy/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/08-ai-example/04-test-strategy/</guid>
      <description>&lt;h1 id=&#34;0804stratégie-de-test&#34;&gt;08.04 — Stratégie de test&lt;a class=&#34;anchor&#34; href=&#34;#0804strat%c3%a9gie-de-test&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Six &lt;strong&gt;types de tests&lt;/strong&gt;, six rôles distincts. Documentés dans &lt;code&gt;CURA_TEST_STRATEGY.md&lt;/code&gt; §3.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;types-de-test&#34;&gt;Types de test&lt;a class=&#34;anchor&#34; href=&#34;#types-de-test&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Type&lt;/th&gt;&#xA;          &lt;th&gt;Statut attendu&lt;/th&gt;&#xA;          &lt;th&gt;Source d&amp;rsquo;autorité&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Happy path&lt;/strong&gt; (cas passant)&lt;/td&gt;&#xA;          &lt;td&gt;PASS&lt;/td&gt;&#xA;          &lt;td&gt;Parcours nominaux décrits d&amp;rsquo;après les SFD&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Unhappy path&lt;/strong&gt; (cas non passant)&lt;/td&gt;&#xA;          &lt;td&gt;PASS (le test passe quand l&amp;rsquo;application refuse de &amp;ldquo;laisser passer&amp;rdquo;)&lt;/td&gt;&#xA;          &lt;td&gt;Validations existantes correctes&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Edge case / boundary&lt;/strong&gt; (tests aux limites)&lt;/td&gt;&#xA;          &lt;td&gt;PASS ou FAIL&lt;/td&gt;&#xA;          &lt;td&gt;Selon le test, souvent accompagné d&amp;rsquo;une note ajoutée sur les SFD&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Business logic vulnerability / gap test&lt;/strong&gt; (tests fonctionnels pour essayer de faire &amp;ldquo;tomber&amp;rdquo; le produit et d&amp;rsquo;identifier des gaps)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;FAIL intentionnel&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Comportements qu&amp;rsquo;un système devrait empêcher mais que CURA n&amp;rsquo;empêche pas&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Exploratory / observed-behaviour&lt;/strong&gt; (exploratoire)&lt;/td&gt;&#xA;          &lt;td&gt;PASS&lt;/td&gt;&#xA;          &lt;td&gt;Documente le comportement actuel quand la spec ne le couvre pas&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Permanent security regression fixture&lt;/strong&gt; (non-régression)&lt;/td&gt;&#xA;          &lt;td&gt;PASS (= la sécurité tient)&lt;/td&gt;&#xA;          &lt;td&gt;Vérifie qu&amp;rsquo;un correctif de sécurité tient (correctifs qui resteront définitivement dans le patrimoine de test)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;happy-path&#34;&gt;Happy path&lt;a class=&#34;anchor&#34; href=&#34;#happy-path&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Exercises the nominal flow with valid inputs and an authenticated user. Verifies that the system produces the correct output (confirmation, history entry, etc.).&lt;/p&gt;</description>
    </item>
    <item>
      <title>09.03.04 — Skills Comprehend</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/03-skills/04-comprehend/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/03-skills/04-comprehend/</guid>
      <description>&lt;h1 id=&#34;090304skills-comprehend&#34;&gt;09.03.04 — Skills Comprehend&lt;a class=&#34;anchor&#34; href=&#34;#090304skills-comprehend&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Skills qui aident l&amp;rsquo;IA à &lt;strong&gt;comprendre&lt;/strong&gt; un projet ou un écosystème avant d&amp;rsquo;agir.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;listing-potentiellement-non-exhaustif&#34;&gt;Listing (potentiellement non exhaustif)&lt;a class=&#34;anchor&#34; href=&#34;#listing-potentiellement-non-exhaustif&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Skill&lt;/th&gt;&#xA;          &lt;th&gt;Cible&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;assess-test-base&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Catalogue la base de test existante&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;assess-ecosystem&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Recherche publique bornée, plafonnée par budget de tokens&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;assess-impact&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Analyse d&amp;rsquo;impact : ce qu&amp;rsquo;un changement touche en aval, à travers le graphe de dépendances&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;understand-sut-constraints&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Comprend les &amp;ldquo;bornes&amp;rdquo; du SUT pour ne pas les dépasser&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;understand-ocarina&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Aiguille selon la classe de question : le Holy Book pour la référence, le livre from-ocarina-to-igor pour l&amp;rsquo;intention ; puis la source d&amp;rsquo;Ocarina + l&amp;rsquo;exemple correspondant à l&amp;rsquo;adaptateur en place&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;understand-ocarina&#34;&gt;&lt;code&gt;understand-ocarina&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#understand-ocarina&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;input  : question de l&amp;#39;utilisateur — aiguillée selon sa classe :&#xA;            - &amp;#34;comment fait-on un match_page ?&amp;#34;   (référence : ce qu&amp;#39;est une primitive) → Holy Book&#xA;            - &amp;#34;pourquoi la suite est-elle bâtie sur Railway ?&amp;#34; (intention / cartographie / pourquoi) → le livre from-ocarina-to-igor&#xA;output : Claude charge, selon la classe de question :&#xA;            - référence    → les pages Holy Book pertinentes (https://mojo-molotov.github.io/ocarina-holy-book)&#xA;            - intention    → les chapitres pertinents du livre from-ocarina-to-igor (https://mojo-molotov.github.io/from-ocarina-to-igor/)&#xA;            - comportement → la source d&amp;#39;Ocarina (clone), si la doc ne le couvre pas&#xA;            - forme        → l&amp;#39;exemple correspondant à l&amp;#39;adaptateur du projet :&#xA;                              Selenium   → ocarina-example, ocarina-with-ai-example&#xA;                              Playwright → ocarina-with-playwright-example&#xA;         puis répond avec citations (tier + URL de page/chapitre ou file:line)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;SKILL.md&lt;/code&gt; :&lt;/p&gt;</description>
    </item>
    <item>
      <title>09.04 — CLAUDE.md et CLAUDE.slim.md</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/04-claude-md/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/04-claude-md/</guid>
      <description>&lt;h1 id=&#34;0904claudemd-et-claudeslimmd&#34;&gt;09.04 — &lt;code&gt;CLAUDE.md&lt;/code&gt; et &lt;code&gt;CLAUDE.slim.md&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0904claudemd-et-claudeslimmd&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Le Holy Book &lt;strong&gt;publie activement&lt;/strong&gt; les fichiers &lt;code&gt;CLAUDE.md&lt;/code&gt;. Cela permet à un LLM de récupérer la doc projet sans passer par le repo.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;les-deux-fichiers&#34;&gt;Les deux fichiers&lt;a class=&#34;anchor&#34; href=&#34;#les-deux-fichiers&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ai/&#xA;├── CLAUDE.md       # version complète (règles + organisation projet)&#xA;└── CLAUDE.slim.md  # version courte (règles uniquement)&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;urls-publiques&#34;&gt;URLs publiques&lt;a class=&#34;anchor&#34; href=&#34;#urls-publiques&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;https://mojo-molotov.github.io/ocarina-holy-book/CLAUDE.md&#xA;https://mojo-molotov.github.io/ocarina-holy-book/CLAUDE.slim.md&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Citation du Holy Book (chapitre « &lt;em&gt;Utiliser Ocarina avec l&amp;rsquo;IA&lt;/em&gt; ») :&lt;/p&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Slim quand le contexte est chargé ; complet pour l&amp;rsquo;onboarding et les revues. En cas de divergence, le complet l&amp;rsquo;emporte.&lt;/p&gt;</description>
    </item>
    <item>
      <title>10.04 — Workflows ocarina-with-ai-example</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/04-ai-workflows/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/04-ai-workflows/</guid>
      <description>&lt;h1 id=&#34;1004workflows-ocarina-with-ai-example&#34;&gt;10.04 — Workflows &lt;code&gt;ocarina-with-ai-example&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#1004workflows-ocarina-with-ai-example&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Voir aussi &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/08-ai-example/09-ci-matrix/&#34;&gt;&lt;code&gt;../08-ai-example/09-ci-matrix.md&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;vue-densemble&#34;&gt;Vue d&amp;rsquo;ensemble&lt;a class=&#34;anchor&#34; href=&#34;#vue-densemble&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Workflow&lt;/th&gt;&#xA;          &lt;th&gt;Trigger&lt;/th&gt;&#xA;          &lt;th&gt;OS&lt;/th&gt;&#xA;          &lt;th&gt;Effet&lt;/th&gt;&#xA;          &lt;th&gt;Note&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ai_proof_ci.yml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;push main, PR, dispatch&lt;/td&gt;&#xA;          &lt;td&gt;ubuntu × py 3.14&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ruff format src/ --check&lt;/code&gt; + &lt;code&gt;ruff check src/&lt;/code&gt; + &lt;code&gt;mypy src/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Gate PR, rapide&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ai_proof_e2e.yml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;dispatch&lt;/td&gt;&#xA;          &lt;td&gt;ubuntu × &lt;strong&gt;matrice firefox+chrome&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;warm-up Heroku + run + sed filter ChromeDriver C++ stacks + upload&lt;/td&gt;&#xA;          &lt;td&gt;Manuel uniquement&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;différences-avec-ocarina-example&#34;&gt;Différences avec &lt;code&gt;ocarina-example&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#diff%c3%a9rences-avec-ocarina-example&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Aspect&lt;/th&gt;&#xA;          &lt;th&gt;&lt;code&gt;ocarina-example&lt;/code&gt;&lt;/th&gt;&#xA;          &lt;th&gt;&lt;code&gt;ocarina-with-ai-example&lt;/code&gt;&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Matrice browser&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Firefox seul (e2e.yml)&lt;/td&gt;&#xA;          &lt;td&gt;Firefox &lt;strong&gt;+&lt;/strong&gt; Chrome (parallélisés)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;SUT&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Igoristan (GitHub Pages)&lt;/td&gt;&#xA;          &lt;td&gt;CURA (Heroku eco-dyno)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Pre-run&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;warm-up Redis client&lt;/td&gt;&#xA;          &lt;td&gt;warm-up Heroku dyno&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Filtre output&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;aucun&lt;/em&gt;&lt;/td&gt;&#xA;          &lt;td&gt;sed filtre les stack frames C++ ChromeDriver&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;WAIT_TIMEOUT&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;default 10&lt;/td&gt;&#xA;          &lt;td&gt;15 (compense les latences Heroku)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;warm-up-heroku&#34;&gt;warm-up Heroku&lt;a class=&#34;anchor&#34; href=&#34;#warm-up-heroku&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;curl -sf --retry &lt;span style=&#34;color:#bd93f9&#34;&gt;6&lt;/span&gt; --retry-delay &lt;span style=&#34;color:#bd93f9&#34;&gt;5&lt;/span&gt; --retry-all-errors &lt;span style=&#34;color:#f1fa8c&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  --max-time &lt;span style=&#34;color:#bd93f9&#34;&gt;30&lt;/span&gt; https://katalon-demo-cura.herokuapp.com/ &amp;gt; /dev/null&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Dyno is warm&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;CURA runs on a Heroku dyno that sleeps after inactivity.&#xA;Tests hitting a cold dyno produce timeouts (false fails) and slow&#xA;confirmations (false passes on gap tests). Wake it explicitly&#xA;before any browser opens.&lt;/p&gt;</description>
    </item>
    <item>
      <title>12.04 — Zone-H, IRC, EFnet</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/04-zoneh-irc-efnet/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/04-zoneh-irc-efnet/</guid>
      <description>&lt;h1 id=&#34;1204zone-h-irc-efnet&#34;&gt;12.04 — Zone-H, IRC, EFnet&lt;a class=&#34;anchor&#34; href=&#34;#1204zone-h-irc-efnet&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Trois infrastructures sans lesquelles la scène hackers 1998-2012 n&amp;rsquo;aurait jamais été la même : &lt;strong&gt;Zone-H&lt;/strong&gt; (archive de defaces), &lt;strong&gt;IRC&lt;/strong&gt;, &lt;strong&gt;EFnet&lt;/strong&gt; (le réseau IRC historique). Le Holy Book ne cite Zone-H qu&amp;rsquo;une fois, mais c&amp;rsquo;est un mot-clé qui ouvre toute une infrastructure.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;1-zone-harchive-des-defaces-2002aujourdhui&#34;&gt;1. Zone-H — archive des defaces (2002 → aujourd&amp;rsquo;hui)&lt;a class=&#34;anchor&#34; href=&#34;#1-zone-harchive-des-defaces-2002aujourdhui&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;fondation&#34;&gt;Fondation&lt;a class=&#34;anchor&#34; href=&#34;#fondation&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Date&lt;/strong&gt; : 2 mars 2002.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Siège&lt;/strong&gt; : Estonie.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Fondateur&lt;/strong&gt; : &lt;strong&gt;Roberto Preatoni&lt;/strong&gt; (alias &lt;strong&gt;&lt;code&gt;Sys64738&lt;/code&gt;&lt;/strong&gt;).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;URL&lt;/strong&gt; : &lt;a href=&#34;https://zone-h.org&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;zone-h.org&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;fonction&#34;&gt;Fonction&lt;a class=&#34;anchor&#34; href=&#34;#fonction&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Un attaquant deface un site (remplace la homepage par sa propre version, signée).&lt;/li&gt;&#xA;&lt;li&gt;L&amp;rsquo;attaquant (ou un témoin) soumet l&amp;rsquo;URL à Zone-H.&lt;/li&gt;&#xA;&lt;li&gt;Zone-H modère : vérifie que le deface est authentique.&lt;/li&gt;&#xA;&lt;li&gt;Si OK : le site défiguré (&lt;em&gt;deface&lt;/em&gt;) est &lt;strong&gt;archivé pour toujours&lt;/strong&gt; sur Zone-H (snapshot du HTML+CSS+assets).&lt;/li&gt;&#xA;&lt;li&gt;L&amp;rsquo;auteur du deface signe avec son alias. Le sourçage est public.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h3 id=&#34;impact&#34;&gt;Impact&lt;a class=&#34;anchor&#34; href=&#34;#impact&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;Pendant 10-15 ans, Zone-H a été &lt;strong&gt;le tableau d&amp;rsquo;affichage public&lt;/strong&gt; de la scène &lt;em&gt;grey hat&lt;/em&gt; mondiale. Chaque deface notable était archivé. Pour un &lt;em&gt;script kiddie&lt;/em&gt;, monter dans le classement Zone-H était &lt;em&gt;le&lt;/em&gt; but pour &lt;em&gt;prouver&lt;/em&gt; :&lt;/p&gt;</description>
    </item>
    <item>
      <title>01.05 — Posture politique du projet</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/01-philosophy/05-political-stance/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/01-philosophy/05-political-stance/</guid>
      <description>&lt;h1 id=&#34;0105posture-politique-du-projet&#34;&gt;01.05 — Posture politique du projet&lt;a class=&#34;anchor&#34; href=&#34;#0105posture-politique-du-projet&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Le Holy Book ne sépare pas la philosophie de la &lt;strong&gt;politique du dépôt&lt;/strong&gt; lui-même. Cinq engagements opérationnels en découlent.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;1-incorruptibilité&#34;&gt;1. Incorruptibilité&lt;a class=&#34;anchor&#34; href=&#34;#1-incorruptibilit%c3%a9&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;« &lt;em&gt;Transmettre Ocarina, c&amp;rsquo;est transmettre une voiture dont j&amp;rsquo;ai fait tout l&amp;rsquo;entretien moi-même, pour moi-même, donc très précautionneusement. En revanche, elle est et restera transmise telle quelle. C&amp;rsquo;est ma voiture.&lt;/em&gt; »&lt;/p&gt;&#xA;&lt;p&gt;« &lt;em&gt;J&amp;rsquo;y ai canalisé toute ma colère, pour y mettre tout mon amour.&lt;/em&gt; »&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.03.05 — create_act et ses hooks</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/05-create-act-hooks/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/05-create-act-hooks/</guid>
      <description>&lt;h1 id=&#34;020305create_act-et-ses-hooks&#34;&gt;02.03.05 — &lt;code&gt;create_act&lt;/code&gt; et ses hooks&lt;a class=&#34;anchor&#34; href=&#34;#020305create_act-et-ses-hooks&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing_with_railway/constructors/create_act.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/testing_with_railway/constructors/create_act.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Cette fonction est la &lt;strong&gt;primitive de bas niveau&lt;/strong&gt; qui crée un constructeur de &lt;em&gt;pas de test&lt;/em&gt;. Elle est appelée par l&amp;rsquo;utilisateur &lt;strong&gt;une fois&lt;/strong&gt;, pour créer un verbe &lt;code&gt;act&lt;/code&gt; propre au projet. Selon la complexité du projet, on peut envisager plusieurs &lt;code&gt;act&lt;/code&gt; distincts avec des hooks différents, mais aucun cas d&amp;rsquo;usage encore observé pour ça, donc on préconise de n&amp;rsquo;en créer &lt;strong&gt;qu&amp;rsquo;un seul&lt;/strong&gt;. Ce n&amp;rsquo;est pas un singleton pour autant : c&amp;rsquo;est &lt;em&gt;juste&lt;/em&gt; une convention.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.04.05 — BusinessInvariantValidator vs FrameworkInvariantValidator</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/04-invariants/05-business-vs-framework-validator/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/04-invariants/05-business-vs-framework-validator/</guid>
      <description>&lt;h1 id=&#34;020405businessinvariantvalidator-vs-frameworkinvariantvalidator&#34;&gt;02.04.05 — &lt;code&gt;BusinessInvariantValidator&lt;/code&gt; vs &lt;code&gt;FrameworkInvariantValidator&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#020405businessinvariantvalidator-vs-frameworkinvariantvalidator&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Deux factories — strictement identiques en code — qui existent &lt;strong&gt;uniquement pour signaler l&amp;rsquo;intention&lt;/strong&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;code&#34;&gt;Code&lt;a class=&#34;anchor&#34; href=&#34;#code&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_BaseCustomInvariantValidator&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @staticmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;create&lt;/span&gt;[T](&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        value: T,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        build_chain: Callable[[ValidationStartBlock[T], T], ValidationAssertBlock[T]],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; ValidationAssertBlock[T]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; _create_business_invariant_validator(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            value, name, &lt;span style=&#34;color:#ff79c6&#34;&gt;lambda&lt;/span&gt; chain: build_chain(chain, value)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@final&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;BusinessInvariantValidator&lt;/span&gt;(_BaseCustomInvariantValidator):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&amp;#34;Factory for creating business domain invariant validators.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;    Use this for domain-specific validation logic (e.g., &amp;#34;user must be adult&amp;#34;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;    &amp;#34;order total must match items&amp;#34;).&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;    &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@final&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;FrameworkInvariantValidator&lt;/span&gt;(_BaseCustomInvariantValidator):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&amp;#34;Factory for creating framework-level invariant validators.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;    Use this for technical/framework validation logic (e.g., &amp;#34;config must be valid&amp;#34;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;    &amp;#34;test structure must be correct&amp;#34;).&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;    &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_create_business_invariant_validator&lt;/span&gt;[T](&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    value: T,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    name: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    build_chain: ValidationChainBuilder[T],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; ValidationAssertBlock[T]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    start_block &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; validate(value, name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;name)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; build_chain(start_block)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;pourquoi-deux-classes&#34;&gt;Pourquoi deux classes ?&lt;a class=&#34;anchor&#34; href=&#34;#pourquoi-deux-classes&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;code&gt;BusinessInvariantValidator&lt;/code&gt; &lt;em&gt;signale&lt;/em&gt; qu&amp;rsquo;on valide une règle métier ; &lt;code&gt;FrameworkInvariantValidator&lt;/code&gt;, une règle technique du framework. À la lecture du code, l&amp;rsquo;intention saute aux yeux.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Grep&lt;/strong&gt;. On peut chercher &lt;code&gt;FrameworkInvariantValidator.create&lt;/code&gt; pour trouver &lt;em&gt;tous les invariants framework&lt;/em&gt;. Idem pour les business rules côté projet.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Maintenabilité&lt;/strong&gt;. Si l&amp;rsquo;on veut un jour ajouter du comportement (log différent, stack-trace différente) à l&amp;rsquo;une ou à l&amp;rsquo;autre, on peut le faire sans refactor de tous les call-sites.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;usages&#34;&gt;Usages&lt;a class=&#34;anchor&#34; href=&#34;#usages&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Invariant&lt;/th&gt;&#xA;          &lt;th&gt;Catégorie&lt;/th&gt;&#xA;          &lt;th&gt;Où il vit&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;validate_workers_amount&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Framework&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/custom_invariants/testing/workers.py&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;validate_test_runners_ids&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Framework&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/custom_invariants/testing/oc_test_runners_ids.py&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;validate_test_runners_names&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Framework&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/custom_invariants/testing/oc_test_runners_names.py&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;validate_test_suites_names&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Framework&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/custom_invariants/testing/oc_test_suites_names.py&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;validate_test_campaigns_names&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Framework&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/custom_invariants/testing/oc_test_campaigns_names.py&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;validate_test_cycle_name&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Framework&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/custom_invariants/testing/oc_test_cycles_names.py&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;em&gt;Exemple métier&lt;/em&gt; : « &lt;em&gt;l&amp;rsquo;username doit être l&amp;rsquo;un de la whitelist&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;Business&lt;/td&gt;&#xA;          &lt;td&gt;À écrire côté projet utilisateur&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;em&gt;Exemple métier&lt;/em&gt; : « &lt;em&gt;la date de réservation doit être future et &amp;lt; 1 an&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;Business&lt;/td&gt;&#xA;          &lt;td&gt;À écrire côté projet utilisateur&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;pattern-complet&#34;&gt;Pattern complet&lt;a class=&#34;anchor&#34; href=&#34;#pattern-complet&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/ocarina/custom_invariants/testing/workers.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; ocarina.dsl.invariants.assertions &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; is_not_zero, is_positive&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; ocarina.dsl.invariants.validate &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; FrameworkInvariantValidator&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_workers_amount_chain&lt;/span&gt;(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    chain: ValidationStartBlock[&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    value: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; ValidationAssertBlock[&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    msg &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Value Error: Number of workers must be at least 1 (got: &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;value&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;).&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; chain&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;assert_that(is_positive, msg&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;msg)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;assert_that(is_not_zero, msg&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;msg)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;validate_workers_amount&lt;/span&gt;(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, workers_amount: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;, name: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; ValidationAssertBlock[&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&amp;#34;Validate that workers amount is at least 1.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; FrameworkInvariantValidator&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;create(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        workers_amount, name, _workers_amount_chain&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;&#xA;&lt;li&gt;Une fonction privée &lt;code&gt;_workers_amount_chain(chain, value) -&amp;gt; ValidationAssertBlock&lt;/code&gt; qui définit le &lt;em&gt;quoi&lt;/em&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Une fonction publique &lt;code&gt;validate_workers_amount(*, workers_amount, name)&lt;/code&gt; qui fournit l&amp;rsquo;API et délègue à &lt;code&gt;FrameworkInvariantValidator.create&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Utilisation côté &lt;code&gt;TestSuite.run&lt;/code&gt; : &lt;code&gt;validate_workers_amount(...).execute().raise_if_invalid()&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;quelle-factory-pour-quel-cas&#34;&gt;Quelle factory pour quel cas&lt;a class=&#34;anchor&#34; href=&#34;#quelle-factory-pour-quel-cas&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Holy Book (extrait du chapitre « &lt;em&gt;Extensibilité&lt;/em&gt; ») :&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.05.05 — Saturation des workers</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/05-orchestration/05-saturation/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/05-orchestration/05-saturation/</guid>
      <description>&lt;h1 id=&#34;020505saturation-des-workers&#34;&gt;02.05.05 — Saturation des workers&lt;a class=&#34;anchor&#34; href=&#34;#020505saturation-des-workers&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Mécanisme propre à Ocarina : si une suite a moins de tests que de workers disponibles, &lt;strong&gt;on clone aléatoirement&lt;/strong&gt; des tests jusqu&amp;rsquo;à atteindre le nombre de workers. Les copies sont renommées &lt;code&gt;[COPY 1] &amp;lt;name&amp;gt;&lt;/code&gt;, &lt;code&gt;[COPY 2] &amp;lt;name&amp;gt;&lt;/code&gt;, etc.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;pourquoi&#34;&gt;Pourquoi&lt;a class=&#34;anchor&#34; href=&#34;#pourquoi&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Le Holy Book formalise la motivation dans le chapitre « &lt;em&gt;Premiers obstacles du monde réel&lt;/em&gt; » :&lt;/p&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Son option &lt;code&gt;saturate_workers&lt;/code&gt; permet de forcer du clonage aléatoire de tests à l&amp;rsquo;intérieur d&amp;rsquo;une suite.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.10.05 — Adapters Selenium</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/05-selenium-adapters/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/05-selenium-adapters/</guid>
      <description>&lt;h1 id=&#34;021005adapters-selenium&#34;&gt;02.10.05 — Adapters Selenium&lt;a class=&#34;anchor&#34; href=&#34;#021005adapters-selenium&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Dossier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/tree/main/src/ocarina/infra/selenium&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/infra/selenium/&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Tout ce qui matérialise les abstractions (&lt;code&gt;POMBase&lt;/code&gt;, &lt;code&gt;WebDriversPool&lt;/code&gt;, &lt;code&gt;Screenshotter&lt;/code&gt;) en version Selenium. &lt;strong&gt;Vit hors du DSL pur&lt;/strong&gt; — et depuis la &lt;code&gt;1.1.3&lt;/code&gt; il a un jumeau : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/tree/main/src/ocarina/infra/playwright&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/infra/playwright/&lt;/code&gt;&lt;/a&gt; livre exactement les mêmes contrats en version Playwright. Changer de backend, c&amp;rsquo;est désormais choisir un dossier, pas l&amp;rsquo;écrire.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;fichiers-du-dossier&#34;&gt;Fichiers du dossier&lt;a class=&#34;anchor&#34; href=&#34;#fichiers-du-dossier&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Fichier&lt;/th&gt;&#xA;          &lt;th&gt;Rôle&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;create_driver.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;_build_firefox&lt;/code&gt;, &lt;code&gt;_build_chrome&lt;/code&gt;, &lt;code&gt;_build_edge&lt;/code&gt;, &lt;code&gt;_build_safari&lt;/code&gt; + dispatch&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;create_drivers_pool.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;create_selenium_drivers_pool&lt;/code&gt; (utilise &lt;code&gt;DriverBuilder&lt;/code&gt; + &lt;code&gt;WebDriversPool&lt;/code&gt;)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;create_screenshotter.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;create_selenium_screenshotter&lt;/code&gt; + &lt;code&gt;_selenium_save_full_page&lt;/code&gt; (Firefox-only)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;driver_healthcheck.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;driver_healthcheck(driver)&lt;/code&gt; — ping &lt;code&gt;driver.title&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;mixins.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;SeleniumTitleMixin&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;create_driverpy&#34;&gt;&lt;code&gt;create_driver.py&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#create_driverpy&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_build_firefox&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, profile_path, driver_path, headless, wait_timeout) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; WebDriver:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    service &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; FirefoxService(executable_path&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;driver_path)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    options &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; FirefoxOptions()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; headless:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        options&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;add_argument(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;-headless&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; profile_path:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        options&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;profile &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; FirefoxProfile(profile_path)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    driver &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Firefox(service&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;service, options&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;options)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    driver&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;implicitly_wait(wait_timeout)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; driver&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_build_chrome&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, profile_path, driver_path, headless, wait_timeout) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; WebDriver:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    service &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; ChromeService(executable_path&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;driver_path)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    options &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; ChromeOptions()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; headless:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        options&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;add_argument(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;--headless=new&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; profile_path:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        options&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;add_argument(&lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;--user-data-dir=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;profile_path&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    driver &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Chrome(service&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;service, options&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;options)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    driver&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;implicitly_wait(wait_timeout)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; driver&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_build_edge&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, profile_path, driver_path, headless, wait_timeout) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; WebDriver:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#6272a4&#34;&gt;# … identique à Chrome avec EdgeService/EdgeOptions/Edge …&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_build_safari&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, profile_path, driver_path, headless, wait_timeout) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; WebDriver:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#6272a4&#34;&gt;# … Safari ne supporte ni driver_path (utilise safaridriver natif macOS),&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#6272a4&#34;&gt;#   ni profile_path (pas de profile custom), ni headless …&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;1-implicitly_wait-défini-une-seule-fois&#34;&gt;1. &lt;code&gt;implicitly_wait&lt;/code&gt; défini une seule fois&lt;a class=&#34;anchor&#34; href=&#34;#1-implicitly_wait-d%c3%a9fini-une-seule-fois&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;Chaque builder appelle &lt;code&gt;driver.implicitly_wait(wait_timeout)&lt;/code&gt; &lt;em&gt;une&lt;/em&gt; fois. Donc, tout &lt;code&gt;find_element&lt;/code&gt; qui ne trouve pas l&amp;rsquo;élément immédiatement attend jusqu&amp;rsquo;à &lt;code&gt;wait_timeout&lt;/code&gt; secondes avant de lever &lt;code&gt;NoSuchElementException&lt;/code&gt;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.11.05 — Plugins de rapport</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/11-opinionated/05-plugins-reports/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/11-opinionated/05-plugins-reports/</guid>
      <description>&lt;h1 id=&#34;021105plugins-de-rapport&#34;&gt;02.11.05 — Plugins de rapport&lt;a class=&#34;anchor&#34; href=&#34;#021105plugins-de-rapport&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Dossier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/tree/main/src/ocarina/opinionated/plugins/reports&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/opinionated/plugins/reports/&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;pretty_print_results&lt;/code&gt;, &lt;code&gt;results_to_json&lt;/code&gt;, &lt;code&gt;generate_docx_proof&lt;/code&gt;, &lt;code&gt;timing&lt;/code&gt;. Tous opt-in. Exécutables séquentiellement ou en parallèle via &lt;code&gt;run_plugins&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;pretty_print_results&#34;&gt;&lt;code&gt;pretty_print_results&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#pretty_print_results&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/ocarina/opinionated/plugins/reports/pretty_print_results.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;pretty_print_results&lt;/span&gt;(results: TestCycleResults, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, with_colors: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;False&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&amp;#34;Display the full test results.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Campaign&#xA;• Suite&#xA;  &amp;gt; Test case 1&#xA;    » PASSED&#xA;  &amp;gt; Test case 2&#xA;    » FAILED&#xA;      → Error message&#xA;        ⫸ At step 3&#xA;  &amp;gt; Test case 3&#xA;    » SKIPPED&#xA;&#xA;Test results:&#xA;1 FAILED | 1 PASSED | 1 SKIPPED&lt;/code&gt;&lt;/pre&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Aspect&lt;/th&gt;&#xA;          &lt;th&gt;Détail&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Hiérarchie&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;3 niveaux : campagne &lt;code&gt;•&lt;/code&gt;, suite &lt;code&gt;&amp;gt;&lt;/code&gt;, cas &lt;code&gt;»&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Niveaux&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;PASSED&lt;/code&gt; (vert), &lt;code&gt;FAILED&lt;/code&gt; (rouge), &lt;code&gt;SKIPPED&lt;/code&gt; (gris).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Erreur&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Message + step number où ça a fail (« &lt;em&gt;At step 3&lt;/em&gt; »)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Comptage agrégé en bas&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;results_to_json&#34;&gt;&lt;code&gt;results_to_json&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#results_to_json&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;generate_json_results&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, results: TestCycleResults, output_dir: Path, logger: ILogger) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&amp;#34;Write test results to a JSON file in results_dir.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    output_dir&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;mkdir(parents&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;True&lt;/span&gt;, exist_ok&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;True&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    max_stem_length &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;8&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    max_attempts &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;500&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; _ &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;range&lt;/span&gt;(max_attempts):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        filename &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;uuid&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;uuid4()&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;hex[:max_stem_length]&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;.json&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        file_path &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; output_dir &lt;span style=&#34;color:#ff79c6&#34;&gt;/&lt;/span&gt; filename&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;not&lt;/span&gt; file_path&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;exists():&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;break&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;else&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;raise&lt;/span&gt; RuntimeError(&lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Can&amp;#39;t generate unique JSON filename in: &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;output_dir&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;.&amp;#34;&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;Campaign&amp;#34;&lt;/span&gt;: {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;Suite&amp;#34;&lt;/span&gt;: {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;test_case&amp;#34;&lt;/span&gt;: [&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;status&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;success&amp;#34;&lt;/span&gt; | &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;fail&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;error&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;...&amp;#34;&lt;/span&gt;},&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        counter,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        metadata&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      ]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_result_to_serializable&lt;/span&gt;(v: Any) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;dict&lt;/span&gt;[&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; is_ok(v):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; {&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;status&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;success&amp;#34;&lt;/span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; is_fail(v):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; {&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;status&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;fail&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;error&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;(v&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;error)}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;raise&lt;/span&gt; TypeError(&lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Expected Ok or Fail instances, but got: &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt;(v)&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le filename est un UUID hex de 8 chars → 16⁸ ≈ 4 milliards d&amp;rsquo;options. Pas de collision dans la pratique. 500 retries de sécurité.&lt;/p&gt;</description>
    </item>
    <item>
      <title>03.05 — Programmation déclarative</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/03-functional/05-declarative/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/03-functional/05-declarative/</guid>
      <description>&lt;h1 id=&#34;0305programmation-déclarative&#34;&gt;03.05 — Programmation déclarative&lt;a class=&#34;anchor&#34; href=&#34;#0305programmation-d%c3%a9clarative&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Un scénario d&amp;rsquo;Ocarina &lt;strong&gt;décrit&lt;/strong&gt;, il n&amp;rsquo;exécute pas. C&amp;rsquo;est ce qui le rend factorisable, multipliable, et lisible.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;démonstration&#34;&gt;Démonstration&lt;a class=&#34;anchor&#34; href=&#34;#d%c3%a9monstration&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; [&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    drive_page(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        act(on_homepage, open_then_verify_homepage)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;failure(just_log_error(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Failed to reach the homepage...&amp;#34;&lt;/span&gt;))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;success(log_success_with_current_url_and_take_screenshot(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;On the homepage!&amp;#34;&lt;/span&gt;)),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        act(on_homepage, click_book_call_page_cta)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;failure(just_log_error(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Failed to click on the &amp;#39;Book a call&amp;#39; CTA...&amp;#34;&lt;/span&gt;))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;success(just_log_success(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Clicked on the &amp;#39;Book a call&amp;#39; CTA!&amp;#34;&lt;/span&gt;)),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    drive_page(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        act(on_book_a_call_page, verify_book_call_page)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;failure(just_log_error(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Failed to verify the &amp;#39;Book a call&amp;#39; page...&amp;#34;&lt;/span&gt;))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;success(log_success_with_current_url_and_take_screenshot(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;On the &amp;#39;Book a call&amp;#39; page!&amp;#34;&lt;/span&gt;)),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ce code &lt;strong&gt;n&amp;rsquo;exécute rien&lt;/strong&gt;. Il &lt;strong&gt;décrit&lt;/strong&gt; :&lt;/p&gt;</description>
    </item>
    <item>
      <title>04.05 — Snapshot testing (syrupy)</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/05-syrupy-snapshots/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/05-syrupy-snapshots/</guid>
      <description>&lt;h1 id=&#34;0405snapshot-testing-syrupy&#34;&gt;04.05 — Snapshot testing (&lt;code&gt;syrupy&lt;/code&gt;)&lt;a class=&#34;anchor&#34; href=&#34;#0405snapshot-testing-syrupy&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;&lt;code&gt;syrupy&lt;/code&gt; est un plugin pytest qui sérialise la sortie d&amp;rsquo;un test dans un fichier &lt;code&gt;.ambr&lt;/code&gt; et compare aux runs suivants. Utilisé pour la sortie de &lt;code&gt;pretty_print_results&lt;/code&gt; et &lt;code&gt;results_to_json&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;ambr&#34;&gt;&lt;code&gt;.ambr&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#ambr&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;tests/opinionated/plugins/reports/__snapshots__/&#xA;├── test_pretty_print_results.ambr&#xA;└── test_results_to_json.ambr&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Généré et géré par &lt;code&gt;syrupy&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;exemple&#34;&gt;Exemple&lt;a class=&#34;anchor&#34; href=&#34;#exemple&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# tests/opinionated/plugins/reports/test_pretty_print_results.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;test_pretty_print_results_renders_campaign_suite_test&lt;/span&gt;(snapshot, capsys):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    results &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Dashboard&amp;#34;&lt;/span&gt;: {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Login happy paths&amp;#34;&lt;/span&gt;: {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Login - without OTP&amp;#34;&lt;/span&gt;: (Ok(&lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;), &lt;span style=&#34;color:#bd93f9&#34;&gt;5&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;login_no_otp&amp;#34;&lt;/span&gt;),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Login - with OTP&amp;#34;&lt;/span&gt;: (Fail(error&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;RuntimeError(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;OTP missed&amp;#34;&lt;/span&gt;)), &lt;span style=&#34;color:#bd93f9&#34;&gt;8&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;login_otp&amp;#34;&lt;/span&gt;),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Skipped one&amp;#34;&lt;/span&gt;: (&lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;skipped_one&amp;#34;&lt;/span&gt;),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    pretty_print_results(results, with_colors&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;False&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    out &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; capsys&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;readouterr()&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;out&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;assert&lt;/span&gt; out &lt;span style=&#34;color:#ff79c6&#34;&gt;==&lt;/span&gt; snapshot&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le &lt;code&gt;snapshot&lt;/code&gt; (fixture de &lt;code&gt;syrupy&lt;/code&gt;) :&lt;/p&gt;</description>
    </item>
    <item>
      <title>05.05 — Pipelines wireit</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/05-igoristan/05-wireit/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/05-igoristan/05-wireit/</guid>
      <description>&lt;h1 id=&#34;0505pipelines-wireit&#34;&gt;05.05 — Pipelines &lt;code&gt;wireit&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0505pipelines-wireit&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;&lt;code&gt;wireit&lt;/code&gt; est un orchestrateur de scripts NPM avec cache incrémental. L&amp;rsquo;Igoristan l&amp;rsquo;utilise pour &lt;em&gt;enchaîner&lt;/em&gt; &lt;code&gt;format-check&lt;/code&gt;, &lt;code&gt;lint&lt;/code&gt;, &lt;code&gt;typecheck&lt;/code&gt;, &lt;code&gt;prebuild&lt;/code&gt;, &lt;code&gt;build&lt;/code&gt;, &lt;code&gt;dev&lt;/code&gt;, &lt;code&gt;preview&lt;/code&gt;, le tout avec un cache fin par tâche.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;packagejsonwireit&#34;&gt;&lt;code&gt;package.json#wireit&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#packagejsonwireit&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;wireit&amp;#34;&lt;/span&gt;: {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;ci:format-check&amp;#34;&lt;/span&gt;: {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;command&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;prettier . --check&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;files&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;*.{js,ts,jsx,tsx,json,md,mdx,html,css,scss,yml,yaml}&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;output&amp;#34;&lt;/span&gt;: []&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;clean:dist&amp;#34;&lt;/span&gt;: {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;command&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;rimraf dist&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;files&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;dist/&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;output&amp;#34;&lt;/span&gt;: []&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;prebuild&amp;#34;&lt;/span&gt;: {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;files&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/**/*.*&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;package.json&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;packages/**/*.*&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;packages/**/package.json&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;vite.config.ts&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;dependencies&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;lint&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;typecheck&amp;#34;&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;preview&amp;#34;&lt;/span&gt;: {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;command&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;vike preview&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;dependencies&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;build&amp;#34;&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;dev&amp;#34;&lt;/span&gt;: {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;command&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;vike dev&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;files&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/**/*.*&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;packages/**/*.*&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;dependencies&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;prebuild&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;output&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;dist/&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;packageLocks&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;pnpm-lock.yaml&amp;#34;&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;build&amp;#34;&lt;/span&gt;: {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;command&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;vike build&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;files&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/**/*.*&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;packages/**/*.*&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;dependencies&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;prebuild&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;output&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;dist/&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;packageLocks&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;pnpm-lock.yaml&amp;#34;&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;lint&amp;#34;&lt;/span&gt;: {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;command&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;eslint \&amp;#34;./{src,packages}/**/*.{js,jsx,ts,tsx}\&amp;#34; --max-warnings 0 --cache&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;files&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/**/*.*&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;package.json&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;packages/**/*.*&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;eslint.config.ts&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;output&amp;#34;&lt;/span&gt;: []&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;typecheck&amp;#34;&lt;/span&gt;: {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;command&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;tsc --build --pretty tsconfig.json&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;files&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/**/*.*&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;package.json&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;tsconfig.app.json&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;tsconfig.json&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;tsconfig.node.json&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;output&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;typecheck-dist/&amp;#34;&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;dag&#34;&gt;DAG&lt;a class=&#34;anchor&#34; href=&#34;#dag&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;                 ┌─────────────────┐&#xA;                 │ ci:format-check │ ────► prettier . --check&#xA;                 └─────────────────┘&#xA;                 ┌─────────────────┐&#xA;                 │     lint        │ ────► eslint (max-warnings 0, --cache)&#xA;                 └─────┬───────────┘&#xA;                       │&#xA;                 ┌─────▼───────────┐&#xA;                 │   typecheck     │ ────► tsc --build --pretty&#xA;                 └─────┬───────────┘&#xA;                       │&#xA;                 ┌─────▼───────────┐&#xA;                 │   prebuild      │ ────► deps: [lint, typecheck]&#xA;                 └─────┬───────────┘&#xA;                       │&#xA;            ┌──────────┴──────────┐&#xA;            ▼                     ▼&#xA;   ┌─────────────────┐    ┌─────────────────┐&#xA;   │   build         │    │   dev           │&#xA;   │   vike build    │    │   vike dev      │&#xA;   └────┬────────────┘    └─────────────────┘&#xA;        ▼&#xA;   ┌─────────────────┐&#xA;   │   preview       │&#xA;   │   vike preview  │&#xA;   └─────────────────┘&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Avec &lt;code&gt;pnpm dev&lt;/code&gt; :&lt;/p&gt;</description>
    </item>
    <item>
      <title>06.05 — isAuthorized.ts</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/06-tests-workers/05-is-authorized/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/06-tests-workers/05-is-authorized/</guid>
      <description>&lt;h1 id=&#34;0605isauthorizedts&#34;&gt;06.05 — &lt;code&gt;isAuthorized.ts&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0605isauthorizedts&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/tests-workers/blob/main/lib/isAuthorized.ts&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;lib/isAuthorized.ts&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Vérifie qu&amp;rsquo;une requête contient le bon &lt;code&gt;x-api-key&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;code&#34;&gt;Code&lt;a class=&#34;anchor&#34; href=&#34;#code&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;type&lt;/span&gt; { NextRequest } &lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;next/server&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; API_SECRET &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; process.env.API_SECRET;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;function&lt;/span&gt; isAuthorized(request: &lt;span style=&#34;color:#8be9fd&#34;&gt;NextRequest&lt;/span&gt;)&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;boolean&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#ff79c6&#34;&gt;typeof&lt;/span&gt; API_SECRET &lt;span style=&#34;color:#ff79c6&#34;&gt;!==&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;string&amp;#34;&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    console.error(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;[CONFIG ERROR] API_SECRET is not defined&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;false&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; { searchParams } &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;new&lt;/span&gt; URL(request.url);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; apiKey &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; searchParams.&lt;span style=&#34;color:#ff79c6&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;apiKey&amp;#34;&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;??&lt;/span&gt; request.headers.&lt;span style=&#34;color:#ff79c6&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;x-api-key&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; apiKey &lt;span style=&#34;color:#ff79c6&#34;&gt;===&lt;/span&gt; API_SECRET;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;default&lt;/span&gt; isAuthorized;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;approche&#34;&gt;Approche&lt;a class=&#34;anchor&#34; href=&#34;#approche&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;1-api_secret&#34;&gt;1. &lt;code&gt;API_SECRET&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#1-api_secret&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; API_SECRET &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; process.env.API_SECRET;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Lu une seule fois, au démarrage du worker &lt;em&gt;Edge&lt;/em&gt; (&lt;em&gt;cold start&lt;/em&gt;).&lt;br&gt;&#xA;Stocké en tant que variable de module.&lt;/p&gt;</description>
    </item>
    <item>
      <title>07.05 — Scénarios randomness (4 levels)</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/05-scenarios-randomness/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/05-scenarios-randomness/</guid>
      <description>&lt;h1 id=&#34;0705scénarios-randomness-4-levels&#34;&gt;07.05 — Scénarios randomness (4 levels)&lt;a class=&#34;anchor&#34; href=&#34;#0705sc%c3%a9narios-randomness-4-levels&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Quatre niveaux de chaos progressifs. On commence simple, on monte en complexité, on stresse de plus en plus Ocarina.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;campagne&#34;&gt;Campagne&lt;a class=&#34;anchor&#34; href=&#34;#campagne&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/tests/campaigns/randomness.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;create_igoristan_randomness_campaign&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, drivers_pool) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; TestCampaign:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; TestCampaign(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Randomness&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        suites&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;[create_randomness_test_suite(drivers_pool&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;drivers_pool)],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;suite&#34;&gt;Suite&lt;a class=&#34;anchor&#34; href=&#34;#suite&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/tests/suites/randomness.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;create_randomness_test_suite&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, drivers_pool) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; TestSuite:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; TestSuite(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Randomness&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        tests&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;[&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            test_random_error_page,                     &lt;span style=&#34;color:#6272a4&#34;&gt;# level 1&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            test_random_loaders_page,                   &lt;span style=&#34;color:#6272a4&#34;&gt;# level 1&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            test_dsed,                                  &lt;span style=&#34;color:#6272a4&#34;&gt;# level 2&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            test_madness,                               &lt;span style=&#34;color:#6272a4&#34;&gt;# level 2&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            test_chaotic_form,                          &lt;span style=&#34;color:#6272a4&#34;&gt;# level 3&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            test_walkthrough,                           &lt;span style=&#34;color:#6272a4&#34;&gt;# level 4&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        drivers_pool&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;drivers_pool,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;levels&#34;&gt;Levels&lt;a class=&#34;anchor&#34; href=&#34;#levels&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Level&lt;/th&gt;&#xA;          &lt;th&gt;Mécanique exercée&lt;/th&gt;&#xA;          &lt;th&gt;Difficulté&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;1&lt;/td&gt;&#xA;          &lt;td&gt;DSL de base (&lt;code&gt;drive_page&lt;/code&gt;, &lt;code&gt;act&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;facile&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;2&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;match_page&lt;/code&gt; / &lt;code&gt;when&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;moyen&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;3&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;HumanizedDriver&lt;/code&gt; + &lt;code&gt;Watcher&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;difficile&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;4&lt;/td&gt;&#xA;          &lt;td&gt;Walkthrough multi-pages chaotiques&lt;/td&gt;&#xA;          &lt;td&gt;difficile&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;level-1random-error-page&#34;&gt;Level 1 — Random Error Page&lt;a class=&#34;anchor&#34; href=&#34;#level-1random-error-page&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# tests/scenarios/randomness/level_1/random_error_page.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;scenario_random_error_page&lt;/span&gt;(driver, logger):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    page &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; RandomErrorPage(driver&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;driver)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; [&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        drive_page(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(page, open_random_error_page)&lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(page, verify_random_error_page)&lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;test_random_error_page &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; create_selenium_test(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Random Error Page - smoke&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    test_scenario&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;lambda&lt;/span&gt; driver, logger: Scenario(test_chain&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;scenario_random_error_page(driver, logger)),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Test volontairement &lt;em&gt;flaky&lt;/em&gt;, exerce le hook &lt;code&gt;on_failure&lt;/code&gt;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>08.05 — Gaps sécurité (G-SEC-1 à G-SEC-3)</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/08-ai-example/05-security-gaps/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/08-ai-example/05-security-gaps/</guid>
      <description>&lt;h1 id=&#34;0805gaps-sécurité-g-sec-1-à-g-sec-3&#34;&gt;08.05 — Gaps sécurité (G-SEC-1 à G-SEC-3)&lt;a class=&#34;anchor&#34; href=&#34;#0805gaps-s%c3%a9curit%c3%a9-g-sec-1-%c3%a0-g-sec-3&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Trois gaps de sécurité documentés. Pas exploités activement (cf. règle « &lt;em&gt;security testing is functional and static, never active&lt;/em&gt; »), mais observés via tests fonctionnels.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;g-sec-1pas-de-jeton-csrf-sur-les-formulaires-côté-front-end&#34;&gt;G-SEC-1 — Pas de jeton CSRF sur les formulaires côté front-end&lt;a class=&#34;anchor&#34; href=&#34;#g-sec-1pas-de-jeton-csrf-sur-les-formulaires-c%c3%b4t%c3%a9-front-end&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;symptôme&#34;&gt;Symptôme&lt;a class=&#34;anchor&#34; href=&#34;#sympt%c3%b4me&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;Le &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; rendu côté Heroku &lt;strong&gt;ne contient pas&lt;/strong&gt; de CSRF token.&lt;/p&gt;&#xA;&lt;h3 id=&#34;source&#34;&gt;Source&lt;a class=&#34;anchor&#34; href=&#34;#source&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;&lt;code&gt;views/page_appointment.php&lt;/code&gt; du repo GitHub &lt;em&gt;appelle&lt;/em&gt; &lt;code&gt;$antiCSRF-&amp;gt;insertHiddenToken()&lt;/code&gt; avant le bouton submit. Mais sur la &lt;strong&gt;version déployée&lt;/strong&gt;, ce token n&amp;rsquo;apparaît &lt;strong&gt;pas&lt;/strong&gt; dans le HTML.&lt;/p&gt;</description>
    </item>
    <item>
      <title>09.03.05 — Skills Pick</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/03-skills/05-pick/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/03-skills/05-pick/</guid>
      <description>&lt;h1 id=&#34;090305skills-pick&#34;&gt;09.03.05 — Skills Pick&lt;a class=&#34;anchor&#34; href=&#34;#090305skills-pick&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Sélection d&amp;rsquo;artefacts (screenshots, logs, reports). &lt;strong&gt;Toujours par mtime, jamais par nom de fichier.&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;listing-potentiellement-non-exhaustif&#34;&gt;Listing (potentiellement non exhaustif)&lt;a class=&#34;anchor&#34; href=&#34;#listing-potentiellement-non-exhaustif&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Skill&lt;/th&gt;&#xA;          &lt;th&gt;Cible&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;pick-screenshots&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Pioche les screenshots les plus récents (par mtime), en les contextualisant à l&amp;rsquo;aide de logs si possible&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;pick-logs&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Pioche les logs les plus récents (par mtime)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;pick-reports&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Pioche les reports DOCX/JSON les plus récents (par mtime)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;mtime&#34;&gt;« &lt;em&gt;mtime&lt;/em&gt; »&lt;a class=&#34;anchor&#34; href=&#34;#mtime&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Citation du Holy Book :&lt;/p&gt;</description>
    </item>
    <item>
      <title>09.05 — Génération de PDF (generate-books)</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/05-pdf-generation/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/05-pdf-generation/</guid>
      <description>&lt;h1 id=&#34;0905génération-de-pdf-generate-books&#34;&gt;09.05 — Génération de PDF (&lt;code&gt;generate-books&lt;/code&gt;)&lt;a class=&#34;anchor&#34; href=&#34;#0905g%c3%a9n%c3%a9ration-de-pdf-generate-books&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Les PDFs publics (&lt;code&gt;ocarina-ru.pdf&lt;/code&gt;, &lt;code&gt;ocarina-en.pdf&lt;/code&gt;, &lt;code&gt;ocarina-fr.pdf&lt;/code&gt;&amp;hellip;) sont &lt;strong&gt;générés par IA&lt;/strong&gt; à partir du dossier &lt;code&gt;docs/&lt;/code&gt;, d&amp;rsquo;un script Python et d&amp;rsquo;un prompt Markdown.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;arborescence&#34;&gt;Arborescence&lt;a class=&#34;anchor&#34; href=&#34;#arborescence&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;prompts/generate-books/&#xA;├── prompt.md                     # spec (lu par l&amp;#39;IA)&#xA;├── script.py                     # implémentation Python&#xA;├── NotoSans-Regular.ttf&#xA;├── NotoSans-Bold.ttf&#xA;├── NotoSans-Italic.ttf&#xA;├── NotoSans-BoldItalic.ttf&#xA;├── NotoSansMath-Regular.ttf&#xA;├── NotoSansMono-Regular.ttf&#xA;├── NotoSansSymbols2-Regular.ttf&#xA;├── NotoNaskhArabic-Regular.ttf&#xA;└── NotoColorEmoji-Regular.ttf&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;1-un-promptmd--un-scriptpy&#34;&gt;1. Un &lt;code&gt;prompt.md&lt;/code&gt; + un &lt;code&gt;script.py&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#1-un-promptmd--un-scriptpy&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;&lt;code&gt;prompt.md&lt;/code&gt; :&lt;/p&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;h1 id=&#34;ocarina-pdf-generator&#34;&gt;Ocarina PDF Generator&lt;a class=&#34;anchor&#34; href=&#34;#ocarina-pdf-generator&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;h2 id=&#34;what-this-is&#34;&gt;What this is&lt;a class=&#34;anchor&#34; href=&#34;#what-this-is&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;A system that turns a VitePress &lt;code&gt;docs/&lt;/code&gt; folder (Markdown + images) into three polished A4 PDFs — one Russian, one English, one French. The system is two files: this prompt (the spec) and &lt;code&gt;script.py&lt;/code&gt; (the implementation). They are designed to be used together in an agentic environment.&lt;/p&gt;</description>
    </item>
    <item>
      <title>10.05 — Workflows igoristan</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/05-igoristan-workflows/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/05-igoristan-workflows/</guid>
      <description>&lt;h1 id=&#34;1005workflows-igoristan&#34;&gt;10.05 — Workflows &lt;code&gt;igoristan&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#1005workflows-igoristan&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Voir aussi &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/05-igoristan/07-ci-deploy/&#34;&gt;&lt;code&gt;../05-igoristan/07-ci-deploy.md&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;vue-densemble&#34;&gt;Vue d&amp;rsquo;ensemble&lt;a class=&#34;anchor&#34; href=&#34;#vue-densemble&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Workflow&lt;/th&gt;&#xA;          &lt;th&gt;Trigger&lt;/th&gt;&#xA;          &lt;th&gt;OS&lt;/th&gt;&#xA;          &lt;th&gt;Effet&lt;/th&gt;&#xA;          &lt;th&gt;Note&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ci-pr.yml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;PR &lt;code&gt;**&lt;/code&gt;, dispatch&lt;/td&gt;&#xA;          &lt;td&gt;ubuntu × node 24 + pnpm 10&lt;/td&gt;&#xA;          &lt;td&gt;3 jobs parallélisés : &lt;code&gt;wireit ci:format-check&lt;/code&gt; + &lt;code&gt;wireit lint&lt;/code&gt; + &lt;code&gt;wireit typecheck&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Gate PR&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;deploy.yml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;push main, dispatch&lt;/td&gt;&#xA;          &lt;td&gt;ubuntu × node 24 + pnpm 10&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;pnpm install --frozen-lockfile&lt;/code&gt; + &lt;code&gt;pnpm build&lt;/code&gt; + upload Pages&lt;/td&gt;&#xA;          &lt;td&gt;Production&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;ci-pryml&#34;&gt;&lt;code&gt;ci-pr.yml&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#ci-pryml&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;jobs&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;format-check&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;env&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;WIREIT_CACHE&lt;/span&gt;: github&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;steps&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: actions/checkout@v6&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: pnpm/action-setup@v5&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt;: { &lt;span style=&#34;color:#ff79c6&#34;&gt;version&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;11&lt;/span&gt; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: actions/setup-node@v6&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt;: { &lt;span style=&#34;color:#ff79c6&#34;&gt;node-version: 24, cache&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;pnpm&amp;#34;&lt;/span&gt; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: google/wireit@setup-github-actions-caching/v2&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;: make install&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Formatcheck&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;: make ci:format-check&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;lint&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;env&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;WIREIT_CACHE&lt;/span&gt;: github&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;steps&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#6272a4&#34;&gt;# ... idem setup ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: actions/cache@v5&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;path&lt;/span&gt;: .eslintcache&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;key&lt;/span&gt;: eslint-cache-${{ hashFiles(&amp;#39;eslint.config.ts&amp;#39;, &amp;#39;src/**/*.*&amp;#39;, &amp;#39;package.json&amp;#39;) }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Lint&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;: pnpm lint&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;typecheck&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;env&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;WIREIT_CACHE&lt;/span&gt;: github&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;steps&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#6272a4&#34;&gt;# ... idem setup ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Typecheck&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;: pnpm typecheck&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Job&lt;/th&gt;&#xA;          &lt;th&gt;Cache supplémentaire&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;format-check&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;wireit&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;lint&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;wireit + &lt;code&gt;.eslintcache&lt;/code&gt; (actions/cache)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;typecheck&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;wireit + &lt;code&gt;tsbuildinfo&lt;/code&gt; (via wireit)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;wireit_cache-github&#34;&gt;&lt;code&gt;WIREIT_CACHE: github&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#wireit_cache-github&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;env&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;WIREIT_CACHE&lt;/span&gt;: github&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;steps&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: google/wireit@setup-github-actions-caching/v2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Active le cache &lt;em&gt;wireit&lt;/em&gt;. Les runs successifs réutilisent les artefacts wireit.&lt;/p&gt;</description>
    </item>
    <item>
      <title>12.05 — Les hackers indonésiens début 2000s</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/05-indonesian-hackers/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/05-indonesian-hackers/</guid>
      <description>&lt;h1 id=&#34;1205les-hackers-indonésiens-début-2000s&#34;&gt;12.05 — Les hackers indonésiens début 2000s&lt;a class=&#34;anchor&#34; href=&#34;#1205les-hackers-indon%c3%a9siens-d%c3%a9but-2000s&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Le Holy Book cite « &lt;em&gt;le premier groupe d&amp;rsquo;hackers indonésiens&lt;/em&gt; » sans le nommer dans le texte principal, mais le mantra cité (« &lt;em&gt;We Can Do All What You Can&amp;rsquo;t Do&lt;/em&gt; ») est bien celui de &lt;strong&gt;YogyaCarderLink (YCL)&lt;/strong&gt;. Reste à expliquer &lt;em&gt;pourquoi&lt;/em&gt; la scène indonésienne de cette époque mérite d&amp;rsquo;être citée dans un manifeste de framework de test en 2026.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;1-les-émeutes-de-mai-1998&#34;&gt;1. Les émeutes de mai 1998&lt;a class=&#34;anchor&#34; href=&#34;#1-les-%c3%a9meutes-de-mai-1998&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;le-déclencheur&#34;&gt;Le déclencheur&lt;a class=&#34;anchor&#34; href=&#34;#le-d%c3%a9clencheur&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;En mai 1998, l&amp;rsquo;Indonésie traverse une crise économique majeure (impact de la crise asiatique de 1997) et une crise politique (fin du régime Suharto). Des émeutes éclatent à Jakarta, Medan, Surakarta, ciblant notamment la &lt;strong&gt;minorité chinoise indonésienne&lt;/strong&gt; (commerçants, classe moyenne). Estimations : ~1000 morts, centaines de viols documentés.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.10.06 — L&#39;acteur Playwright : un thread propriétaire</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/06-playwright-actor/</link>
      <pubDate>Mon, 01 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/06-playwright-actor/</guid>
      <description>&lt;h1 id=&#34;021006lacteur-playwright-un-thread-propriétaire&#34;&gt;02.10.06 — L&amp;rsquo;acteur Playwright : un thread propriétaire&lt;a class=&#34;anchor&#34; href=&#34;#021006lacteur-playwright-un-thread-propri%c3%a9taire&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Dossier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/tree/main/src/ocarina/infra/playwright&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/infra/playwright/&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;L&amp;rsquo;adapter Playwright reprend la structure de l&amp;rsquo;adapter Selenium fichier pour fichier (&lt;code&gt;create_driver&lt;/code&gt;, &lt;code&gt;create_drivers_pool&lt;/code&gt;, &lt;code&gt;create_screenshotter&lt;/code&gt;, &lt;code&gt;driver_healthcheck&lt;/code&gt;, &lt;code&gt;mixins&lt;/code&gt;), avec &lt;strong&gt;un fichier en plus&lt;/strong&gt; : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/infra/playwright/driver.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;driver.py&lt;/code&gt;&lt;/a&gt;. Ce fichier mérite un chapitre à lui seul. C&amp;rsquo;est lui qui réconcilie l&amp;rsquo;API &lt;em&gt;sync&lt;/em&gt; de Playwright, intrinsèquement liée à un thread, avec le modèle threadé d&amp;rsquo;Ocarina (pool, warmup, Watcher).&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;le-problème-lapi-sync-de-playwright-est-thread-affine&#34;&gt;Le problème : l&amp;rsquo;API sync de Playwright est thread-affine&lt;a class=&#34;anchor&#34; href=&#34;#le-probl%c3%a8me-lapi-sync-de-playwright-est-thread-affine&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;L&amp;rsquo;API &lt;strong&gt;sync&lt;/strong&gt; de Playwright lie chaque objet qu&amp;rsquo;elle renvoie (&lt;code&gt;Playwright&lt;/code&gt;, &lt;code&gt;Browser&lt;/code&gt;, &lt;code&gt;BrowserContext&lt;/code&gt;, &lt;code&gt;Page&lt;/code&gt;, &lt;code&gt;Locator&lt;/code&gt;, …) au thread qui a appelé &lt;code&gt;sync_playwright().start()&lt;/code&gt;. Sous le capot, c&amp;rsquo;est un &lt;em&gt;greenlet&lt;/em&gt; épinglé à ce thread. Y toucher depuis un autre thread lève :&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.03.06 — drive_page</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/06-drive-page/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/06-drive-page/</guid>
      <description>&lt;h1 id=&#34;020306drive_page&#34;&gt;02.03.06 — &lt;code&gt;drive_page&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#020306drive_page&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/opinionated/dsl/drive_page.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/opinionated/dsl/drive_page.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;code&#34;&gt;Code&lt;a class=&#34;anchor&#34; href=&#34;#code&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;drive_page&lt;/span&gt;(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    first: ActionSuccess[TPOM], &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;rest: ActionSuccess[TPOM]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; ChainRunner[TPOM]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; chain_actions(first, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;rest)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;C&amp;rsquo;est &lt;strong&gt;exactement&lt;/strong&gt; &lt;code&gt;chain_actions&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;pourquoi-cet-alias&#34;&gt;Pourquoi cet alias ?&lt;a class=&#34;anchor&#34; href=&#34;#pourquoi-cet-alias&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;1-sémantique-je-prends-le-contrôle-d&#34;&gt;1. Sémantique : « &lt;em&gt;je prends le contrôle d&amp;rsquo;une page&lt;/em&gt; »&lt;a class=&#34;anchor&#34; href=&#34;#1-s%c3%a9mantique-je-prends-le-contr%c3%b4le-d&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;Citation du Holy Book (&lt;em&gt;Premiers scénarios&lt;/em&gt;) :&lt;/p&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;&lt;code&gt;drive_page&lt;/code&gt; exprime que l&amp;rsquo;on prend le contrôle d&amp;rsquo;&lt;em&gt;une&lt;/em&gt; page.&#xA;Toute &lt;em&gt;transition&lt;/em&gt; devient explicite par l&amp;rsquo;ouverture d&amp;rsquo;un nouveau &lt;code&gt;drive_page&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; [&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    drive_page(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        act(on_homepage, open_homepage)&lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        act(on_homepage, verify_homepage)&lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        act(on_homepage, click_cta)&lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ),                                          &lt;span style=&#34;color:#6272a4&#34;&gt;# ⬅ fermeture du contrôle de la homepage&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    drive_page(                                 &lt;span style=&#34;color:#6272a4&#34;&gt;# ⬅ ouverture du contrôle de la page suivante&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        act(on_target_page, verify_target_page)&lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;2-discipline&#34;&gt;2. Discipline&lt;a class=&#34;anchor&#34; href=&#34;#2-discipline&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;Mélanger des actes sur deux POMs différents dans un même &lt;code&gt;drive_page&lt;/code&gt; devient une erreur &lt;em&gt;mypy&lt;/em&gt; (cf. &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/02-action-chain-states/&#34;&gt;&lt;code&gt;02-action-chain-states.md&lt;/code&gt;&lt;/a&gt;, section « &lt;em&gt;narrowing par &lt;code&gt;act()&lt;/code&gt;&lt;/em&gt; »). Cela force concrètement à respecter la sémantique.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.04.06 — Hiérarchie d&#39;erreurs des invariants</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/04-invariants/06-invariant-errors/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/04-invariants/06-invariant-errors/</guid>
      <description>&lt;h1 id=&#34;020406hiérarchie-derreurs-des-invariants&#34;&gt;02.04.06 — Hiérarchie d&amp;rsquo;erreurs des invariants&lt;a class=&#34;anchor&#34; href=&#34;#020406hi%c3%a9rarchie-derreurs-des-invariants&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/invariants/errors.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/invariants/errors.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;InvariantViolationError (base — sous-classe d&amp;#39;Exception)&#xA;├── DuplicatesError                  (levée par `has_unique_elements`)&#xA;└── AggregateInvariantViolationError (levée par `_ValidationResult.raise_if_invalid`)&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;invariantviolationerror&#34;&gt;&lt;code&gt;InvariantViolationError&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#invariantviolationerror&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;InvariantViolationError&lt;/span&gt;(Exception):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&amp;#34;Base exception raised when an invariant is violated.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Une simple sous-classe d&amp;rsquo;&lt;code&gt;Exception&lt;/code&gt; sans logique propre. Tous les prédicats lèvent cette classe (ou une sous-classe).&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;try&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    validate(value)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;assert_that(predicate)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;execute()&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;raise_if_invalid()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;except&lt;/span&gt; InvariantViolationError &lt;span style=&#34;color:#ff79c6&#34;&gt;as&lt;/span&gt; e:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logger&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;error(&lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Validation failed: &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;e&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;… attrape &lt;strong&gt;toutes&lt;/strong&gt; les violations en une seule clause.&lt;/p&gt;&#xA;&lt;h2 id=&#34;duplicateserror&#34;&gt;&lt;code&gt;DuplicatesError&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#duplicateserror&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;DuplicatesError&lt;/span&gt;(InvariantViolationError):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;__init__&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, duplicates: Sequence[Any], message: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;duplicates &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; duplicates&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        msg &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; message &lt;span style=&#34;color:#ff79c6&#34;&gt;or&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Duplicate elements detected:&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;join(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34; - &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;d&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; d &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; duplicates&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;super&lt;/span&gt;()&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;__init__&lt;/span&gt;(msg)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;&#xA;&lt;li&gt;Stocke la liste des doublons dans &lt;code&gt;.duplicates&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Génère un message « &lt;em&gt;Duplicate elements detected:&lt;code&gt;\n&lt;/code&gt; - apple&lt;code&gt;\n&lt;/code&gt; - banana&lt;/em&gt; » par défaut.&lt;/li&gt;&#xA;&lt;li&gt;Accepte un &lt;code&gt;message=&lt;/code&gt; custom.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Levée par &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/invariants/assertions.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;has_unique_elements&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.05.06 — TestCampaign[Driver]</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/05-orchestration/06-test-campaign/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/05-orchestration/06-test-campaign/</guid>
      <description>&lt;h1 id=&#34;020506testcampaigndriver&#34;&gt;02.05.06 — &lt;code&gt;TestCampaign[Driver]&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#020506testcampaigndriver&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing/oc_test_campaign.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/testing/oc_test_campaign.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Responsabilité unique&lt;/strong&gt; : exécuter une &lt;strong&gt;séquence de suites&lt;/strong&gt; dans l&amp;rsquo;ordre, partager une config de workers, et déclarer &lt;code&gt;campaign_has_failed&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;code&#34;&gt;Code&lt;a class=&#34;anchor&#34; href=&#34;#code&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;TestCampaign&lt;/span&gt;[Driver]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;__init__&lt;/span&gt;(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        suites: Sequence[TestSuite[Driver]],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        max_workers: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        saturate_workers: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        validate_test_suites_names(suites&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;suites, name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;suites&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;execute()&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;raise_if_invalid()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; name&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_suites &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; suites&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_results: TestCampaignResults &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_max_workers &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; max_workers&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_saturate_workers &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; saturate_workers&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; suite &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_suites:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            suite&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_campaign_name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;run_all&lt;/span&gt;(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, skip_all: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;False&lt;/span&gt;, saturate_workers: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;True&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; TestCampaignResults:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_results&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;clear()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        resolved_saturate_workers &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; (&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_saturate_workers&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_saturate_workers &lt;span style=&#34;color:#ff79c6&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;else&lt;/span&gt; saturate_workers&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; skip_all:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; suite &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_suites:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_results[suite&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    name: (&lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;, test_id)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; name, test_id &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; suite&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;test_names_and_ids&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_results&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; suite &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_suites:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_results[suite&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; suite&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;run(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                max_workers&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_max_workers,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                saturate_workers&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;resolved_saturate_workers,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_results&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;campaign_has_failed&lt;/span&gt;(results: TestCampaignResults) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;bool&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;any&lt;/span&gt;(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        is_test_result_fail(outcome)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; campaign_results &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; results&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;values()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; outcome, _, _ &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; campaign_results&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;values()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;anatomie&#34;&gt;Anatomie&lt;a class=&#34;anchor&#34; href=&#34;#anatomie&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;garde-fou-au-constructeur&#34;&gt;Garde-fou au constructeur&lt;a class=&#34;anchor&#34; href=&#34;#garde-fou-au-constructeur&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;validate_test_suites_names(suites&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;suites, name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;suites&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;execute()&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;raise_if_invalid()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;→ Les noms de suites de la campagne doivent être uniques — de façon insensible à la casse depuis &lt;code&gt;1.1.10&lt;/code&gt; (NFC + casefold). Levé &lt;em&gt;dès la construction&lt;/em&gt;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.06 — Scenario[Driver]</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/06-scenario/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/06-scenario/</guid>
      <description>&lt;h1 id=&#34;0206scenariodriver&#34;&gt;02.06 — &lt;code&gt;Scenario[Driver]&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0206scenariodriver&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/custom_types/scenario.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/custom_types/scenario.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;dataclass&#34;&gt;Dataclass&lt;a class=&#34;anchor&#34; href=&#34;#dataclass&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@final&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@dataclass(frozen&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;True&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;Scenario&lt;/span&gt;[Driver]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    test_chain: TestChain&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    setup: TestSetup &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; field(default&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    teardown: TestTeardown &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; field(default&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    watchers: TestWatchers[Driver] &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; field(default&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/ocarina/custom_types/test_components.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; TestChain &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Sequence[ChainRunner[Any]]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; TestSetup &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Effect &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; TestTeardown &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Effect &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; TestWatchers[Driver] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Sequence[Watcher[Driver]] &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;cycle-de-vie-per-attempt&#34;&gt;Cycle de vie (per attempt)&lt;a class=&#34;anchor&#34; href=&#34;#cycle-de-vie-per-attempt&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;1. setup()           — optionnel, Effect libre (DB, API, …)&#xA;       → lève     :  skip de test_chain, jump à teardown,&#xA;                     return Outcome(setup_failed=True, should_retry=True)&#xA;       → ok       :  continue à test_chain&#xA;&#xA;2. test_chain        — la chaîne réelle (Sequence[ChainRunner])&#xA;       (les watchers tournent pendant ce temps)&#xA;&#xA;3. teardown()        — optionnel, toujours exécuté&#xA;       → lève     :  log warning + ignore (n&amp;#39;affecte pas le verdict)&#xA;&#xA;Si TOUTES les tentatives lèvent au setup :&#xA;    → test marqué SKIPPED (pas FAILED)&#xA;    → log warning « setup keeps failing »&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;setup-et-teardown&#34;&gt;&lt;code&gt;setup&lt;/code&gt; et &lt;code&gt;teardown&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#setup-et-teardown&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;setup&lt;/code&gt; et &lt;code&gt;teardown&lt;/code&gt; sont &lt;strong&gt;driver-free et injectionless by design.&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.11.06 — bootstrap &#43; run_plugins</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/11-opinionated/06-bootstrap-launcher/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/11-opinionated/06-bootstrap-launcher/</guid>
      <description>&lt;h1 id=&#34;021106bootstrap--run_plugins&#34;&gt;02.11.06 — &lt;code&gt;bootstrap&lt;/code&gt; + &lt;code&gt;run_plugins&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#021106bootstrap--run_plugins&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/opinionated/launcher/bootstrap.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/opinionated/launcher/bootstrap.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Point d&amp;rsquo;entrée d&amp;rsquo;un projet Ocarina.&lt;br&gt;&#xA;Trois étapes : &lt;code&gt;cycle.run_all → run_plugins → post_exec&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;code&#34;&gt;Code&lt;a class=&#34;anchor&#34; href=&#34;#code&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;bootstrap&lt;/span&gt;[T](&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    test_cycle: TestCycle[T],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    run_plugins: Callable[[TestCycleResults], &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    post_exec: Callable[[TestCycleResults], &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;] &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    saturate_workers: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;True&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    results &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; test_cycle&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;run_all(saturate_workers&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;saturate_workers)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    run_plugins(results)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; post_exec:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        post_exec(results)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;ordre&#34;&gt;Ordre&lt;a class=&#34;anchor&#34; href=&#34;#ordre&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;1. cycle.run_all()  →  results&#xA;2. run_plugins(results)&#xA;3. post_exec(results) (optionnel)&lt;/code&gt;&lt;/pre&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Étape&lt;/th&gt;&#xA;          &lt;th&gt;Effet&lt;/th&gt;&#xA;          &lt;th&gt;Pourquoi cet ordre&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;1&lt;/td&gt;&#xA;          &lt;td&gt;Tous les tests s&amp;rsquo;exécutent&lt;/td&gt;&#xA;          &lt;td&gt;On a besoin des &lt;code&gt;results&lt;/code&gt; pour la suite&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;2&lt;/td&gt;&#xA;          &lt;td&gt;Les plugins se lancent (DOCX, JSON, etc.)&lt;/td&gt;&#xA;          &lt;td&gt;Doit s&amp;rsquo;exécuter &lt;em&gt;avant&lt;/em&gt; &lt;code&gt;post_exec&lt;/code&gt; car peut avoir besoin du temps de génération&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;3&lt;/td&gt;&#xA;          &lt;td&gt;Post-exec (pretty_print + sys.exit)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Dernière&lt;/em&gt; chose ; après ça, le process est mort&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;typage&#34;&gt;Typage&lt;a class=&#34;anchor&#34; href=&#34;#typage&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;run_plugins: Callable[[TestCycleResults], &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;post_exec: Callable[[TestCycleResults], &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;] &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Les deux reçoivent les &lt;strong&gt;&lt;code&gt;results&lt;/code&gt;&lt;/strong&gt; en argument :&lt;/p&gt;</description>
    </item>
    <item>
      <title>03.06 — Generics PEP 695 dans Ocarina</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/03-functional/06-pep-695-generics/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/03-functional/06-pep-695-generics/</guid>
      <description>&lt;h1 id=&#34;0306generics-pep-695-dans-ocarina&#34;&gt;03.06 — Generics PEP 695 dans Ocarina&lt;a class=&#34;anchor&#34; href=&#34;#0306generics-pep-695-dans-ocarina&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Ocarina exploite à fond la syntaxe générique introduite par PEP 695 (Python 3.12+). C&amp;rsquo;est ce qui rend tout l&amp;rsquo;écosystème typé &lt;em&gt;sans que ce soit le foutoir&lt;/em&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;trois-formes-de-pep-695&#34;&gt;Trois formes de PEP 695&lt;a class=&#34;anchor&#34; href=&#34;#trois-formes-de-pep-695&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;1-classes-génériques&#34;&gt;1. Classes génériques&lt;a class=&#34;anchor&#34; href=&#34;#1-classes-g%c3%a9n%c3%a9riques&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@final&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;Ok&lt;/span&gt;[T](_BaseResult):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    value: T&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    error: &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;TestSuite&lt;/span&gt;[Driver]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;Watcher&lt;/span&gt;[Driver]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;ChainRunner&lt;/span&gt;[T]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;ValidationStartBlock&lt;/span&gt;[T]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;CliStore&lt;/span&gt;[TKeys: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;]:  &lt;span style=&#34;color:#6272a4&#34;&gt;# avec bound&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Avant PEP 695 :&lt;/p&gt;</description>
    </item>
    <item>
      <title>04.06 — Property-based testing (hypothesis)</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/06-hypothesis-properties/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/06-hypothesis-properties/</guid>
      <description>&lt;h1 id=&#34;0406property-based-testing-hypothesis&#34;&gt;04.06 — Property-based testing (&lt;code&gt;hypothesis&lt;/code&gt;)&lt;a class=&#34;anchor&#34; href=&#34;#0406property-based-testing-hypothesis&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Un seul fichier (&lt;code&gt;test_invariants_properties.py&lt;/code&gt;), mais le pattern est intéressant : on &lt;strong&gt;génère&lt;/strong&gt; des inputs et on vérifie des &lt;strong&gt;propriétés universelles&lt;/strong&gt; des prédicats d&amp;rsquo;invariants.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;le-concept&#34;&gt;Le concept&lt;a class=&#34;anchor&#34; href=&#34;#le-concept&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;hypothesis&lt;/code&gt; génère automatiquement des &lt;em&gt;cas de test&lt;/em&gt; qui satisfont des contraintes données, puis vérifie qu&amp;rsquo;une propriété est vraie pour tous les cas générés. En cas d&amp;rsquo;échec, &lt;em&gt;hypothesis&lt;/em&gt; fait du &lt;strong&gt;shrinking&lt;/strong&gt; pour trouver le &lt;strong&gt;plus petit contre-exemple&lt;/strong&gt;.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; hypothesis &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; given, strategies &lt;span style=&#34;color:#ff79c6&#34;&gt;as&lt;/span&gt; st&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@given(st&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;integers())&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;test_is_positive_passes_on_positive&lt;/span&gt;(value):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; value &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        is_positive(value)             &lt;span style=&#34;color:#6272a4&#34;&gt;# ne doit pas lever&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;else&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt; pytest&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;raises(InvariantViolationError):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            is_positive(value)         &lt;span style=&#34;color:#6272a4&#34;&gt;# doit lever&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;→ &lt;code&gt;hypothesis&lt;/code&gt; génère des entiers (typiquement 100 par défaut), vérifie la propriété pour chacun. Si une exception &lt;em&gt;inattendue&lt;/em&gt; surgit, c&amp;rsquo;est un fail. Le shrinker trouve le plus petit cas qui fail (&lt;code&gt;0&lt;/code&gt; ? &lt;code&gt;-1&lt;/code&gt; ? &lt;code&gt;MIN_INT&lt;/code&gt; ?).&lt;/p&gt;</description>
    </item>
    <item>
      <title>05.06 — Husky &#43; lint-staged &#43; commitlint &#43; commitizen</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/05-igoristan/06-husky-commitlint/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/05-igoristan/06-husky-commitlint/</guid>
      <description>&lt;h1 id=&#34;0506husky--lint-staged--commitlint--commitizen&#34;&gt;05.06 — Husky + lint-staged + commitlint + commitizen&lt;a class=&#34;anchor&#34; href=&#34;#0506husky--lint-staged--commitlint--commitizen&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Discipline de commits stricte. Conventionnels, signés, format-checkés.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;outils&#34;&gt;Outils&lt;a class=&#34;anchor&#34; href=&#34;#outils&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Outil&lt;/th&gt;&#xA;          &lt;th&gt;Rôle&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Husky&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Installe les hooks Git locaux (&lt;code&gt;pre-commit&lt;/code&gt;, &lt;code&gt;commit-msg&lt;/code&gt;)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;lint-staged&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Lance des outils &lt;em&gt;seulement&lt;/em&gt; sur les fichiers &lt;em&gt;staged&lt;/em&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;commitlint&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Vérifie que le message de commit suit la convention&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;commitizen + cz-commitlint&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;CLI interactive pour &lt;em&gt;composer&lt;/em&gt; un message conforme&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;config&#34;&gt;Config&lt;a class=&#34;anchor&#34; href=&#34;#config&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;husky&#34;&gt;&lt;code&gt;.husky/&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#husky&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;Dossier contenant les hook scripts :&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;.husky/&#xA;├── pre-commit&#xA;└── commit-msg&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;pre-commit&lt;/code&gt; :&lt;/p&gt;</description>
    </item>
    <item>
      <title>06.06 — lib/redis.ts &#43; Upstash</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/06-tests-workers/06-redis-upstash/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/06-tests-workers/06-redis-upstash/</guid>
      <description>&lt;h1 id=&#34;0606libredists--upstash&#34;&gt;06.06 — &lt;code&gt;lib/redis.ts&lt;/code&gt; + Upstash&lt;a class=&#34;anchor&#34; href=&#34;#0606libredists--upstash&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/tests-workers/blob/main/lib/redis.ts&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;lib/redis.ts&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;code&#34;&gt;Code&lt;a class=&#34;anchor&#34; href=&#34;#code&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; { Redis } &lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;@upstash/redis&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; redis &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;new&lt;/span&gt; Redis({&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  url: &lt;span style=&#34;color:#8be9fd&#34;&gt;process.env.UPSTASH_REDIS_REST_URL&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  token: &lt;span style=&#34;color:#8be9fd&#34;&gt;process.env.UPSTASH_REDIS_REST_TOKEN&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;default&lt;/span&gt; redis;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;pourquoi-upstashredis-plutôt-que-ioredis&#34;&gt;Pourquoi &lt;code&gt;@upstash/redis&lt;/code&gt; plutôt que &lt;code&gt;ioredis&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#pourquoi-upstashredis-plut%c3%b4t-que-ioredis&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Critère&lt;/th&gt;&#xA;          &lt;th&gt;&lt;code&gt;ioredis&lt;/code&gt;&lt;/th&gt;&#xA;          &lt;th&gt;&lt;code&gt;@upstash/redis&lt;/code&gt;&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Protocole&lt;/td&gt;&#xA;          &lt;td&gt;TCP / RESP&lt;/td&gt;&#xA;          &lt;td&gt;HTTP / REST&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Edge runtime&lt;/td&gt;&#xA;          &lt;td&gt;❌ Non&lt;/td&gt;&#xA;          &lt;td&gt;✅ Oui&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Connexion&lt;/td&gt;&#xA;          &lt;td&gt;Persistante&lt;/td&gt;&#xA;          &lt;td&gt;Per-request&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Latence (&lt;em&gt;warm&lt;/em&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;&amp;lt; 1ms&lt;/td&gt;&#xA;          &lt;td&gt;50-100ms&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Latence (&lt;em&gt;cold&lt;/em&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;200ms+&lt;/td&gt;&#xA;          &lt;td&gt;50-100ms&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Coût&lt;/td&gt;&#xA;          &lt;td&gt;Variable&lt;/td&gt;&#xA;          &lt;td&gt;Pay-per-request&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Setup&lt;/td&gt;&#xA;          &lt;td&gt;Self-hosted Redis&lt;/td&gt;&#xA;          &lt;td&gt;Upstash dashboard (managed)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;L&amp;rsquo;&lt;em&gt;Edge runtime&lt;/em&gt; n&amp;rsquo;accepte &lt;strong&gt;pas&lt;/strong&gt; de connexions TCP persistantes (chaque &lt;em&gt;worker&lt;/em&gt; est éphémère).&lt;br&gt;&#xA;Donc : Upstash REST.&lt;/p&gt;</description>
    </item>
    <item>
      <title>07.06 — Scénarios sacred upload</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/06-scenarios-sacred-upload/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/06-scenarios-sacred-upload/</guid>
      <description>&lt;h1 id=&#34;0706scénarios-sacred-upload&#34;&gt;07.06 — Scénarios sacred upload&lt;a class=&#34;anchor&#34; href=&#34;#0706sc%c3%a9narios-sacred-upload&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;La page &lt;code&gt;/igoristan/sacred-upload&lt;/code&gt; permet de glisser-déposer un fichier. Exerce le pattern d&amp;rsquo;&lt;strong&gt;upload de fichiers&lt;/strong&gt; côté Ocarina.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;campagne&#34;&gt;Campagne&lt;a class=&#34;anchor&#34; href=&#34;#campagne&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/tests/campaigns/sacred_upload.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;create_igoristan_sacred_upload_campaign&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, drivers_pool) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; TestCampaign:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; TestCampaign(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Sacred upload&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        suites&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;[&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            create_sacred_upload_happy_paths_test_suite(drivers_pool&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;drivers_pool),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            create_sacred_upload_unhappy_paths_test_suite(drivers_pool&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;drivers_pool),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;scénario-upload-files&#34;&gt;Scénario : upload files&lt;a class=&#34;anchor&#34; href=&#34;#sc%c3%a9nario-upload-files&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# tests/scenarios/sacred_upload/upload_files.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;scenario_upload_files&lt;/span&gt;(driver, logger):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    page &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; SacredUploadPage(driver&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;driver)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    fixture_dir &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Path(&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;__file__&lt;/span&gt;)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;parent&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;parent&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;parent&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;parent &lt;span style=&#34;color:#ff79c6&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;pages&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;sacred_upload&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;fixtures&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    file_to_upload &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; fixture_dir &lt;span style=&#34;color:#ff79c6&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;sample.png&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; [&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        drive_page(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(page, open_sacred_upload_page)&lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(page, verify_sacred_upload_page)&lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(page, upload_file(file_to_upload))&lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(page, verify_file_uploaded(file_to_upload&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name))&lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;test_upload_files &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; create_selenium_test(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Sacred upload - upload a file&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    test_scenario&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;lambda&lt;/span&gt; driver, logger: Scenario(test_chain&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;scenario_upload_files(driver, logger)),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;input-typefile&#34;&gt;&lt;code&gt;&amp;lt;input type=&amp;quot;file&amp;quot;&amp;gt;&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#input-typefile&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;SacredUploadPage&lt;/span&gt;(SeleniumTitleMixin, POMBase):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    _file_input &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; (By&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;CSS_SELECTOR, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;input[type=&amp;#39;file&amp;#39;]&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    _uploaded_preview &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; (By&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;CSS_SELECTOR, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;.upload-preview&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;upload_file&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, file_path: Path) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; SacredUploadPage:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        file_input &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_driver&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;find_element(&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_file_input)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        file_input&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;send_keys(&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;(file_path&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;resolve()))     &lt;span style=&#34;color:#6272a4&#34;&gt;# ← Selenium pattern: send_keys sur input[type=file]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;verify_file_uploaded&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, filename: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; SacredUploadPage:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        WebDriverWait(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_driver, get_timeout())&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;until(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ec&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;text_to_be_present_in_element(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_uploaded_preview, filename)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le &lt;strong&gt;pattern Selenium standard&lt;/strong&gt; : &lt;code&gt;send_keys(&amp;lt;path absolu&amp;gt;)&lt;/code&gt; sur un &lt;code&gt;input[type=file]&lt;/code&gt; &lt;em&gt;même si l&amp;rsquo;input est invisible&lt;/em&gt;. Selenium contourne la sécurité du navigateur pour les inputs file uniquement.&lt;/p&gt;</description>
    </item>
    <item>
      <title>08.06 — Gaps data (G-DATA-1, G-DATA-2)</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/08-ai-example/06-data-gaps/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/08-ai-example/06-data-gaps/</guid>
      <description>&lt;h1 id=&#34;0806gaps-data-g-data-1-g-data-2&#34;&gt;08.06 — Gaps data (G-DATA-1, G-DATA-2)&lt;a class=&#34;anchor&#34; href=&#34;#0806gaps-data-g-data-1-g-data-2&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Deux gaps majeurs sur l&amp;rsquo;intégrité des données.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;g-data-1pas-de-validation-serveur-de-visit_date&#34;&gt;G-DATA-1 — Pas de validation serveur de &lt;code&gt;visit_date&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#g-data-1pas-de-validation-serveur-de-visit_date&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;source&#34;&gt;Source&lt;a class=&#34;anchor&#34; href=&#34;#source&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;// appointment.php&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;array_diff_key(&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$_POST&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;array&lt;/span&gt;(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;facility&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;hospital_readmission&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;programs&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;visit_date&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;comment&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Cette ligne fait juste une vérification de &lt;em&gt;présence des clés&lt;/em&gt;.&lt;br&gt;&#xA;Elle ne valide &lt;strong&gt;jamais&lt;/strong&gt; la valeur de &lt;code&gt;visit_date&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;h3 id=&#34;symptôme&#34;&gt;Symptôme&lt;a class=&#34;anchor&#34; href=&#34;#sympt%c3%b4me&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Empty visit_date&lt;/strong&gt; : si on supprime l&amp;rsquo;attribut HTML5 &lt;code&gt;required&lt;/code&gt; via JS et qu&amp;rsquo;on submit avec une date vide → prise de RDV acceptée.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Past visit_date&lt;/strong&gt; : si on envoie une réservation antidatée → prise de RDV acceptée.&lt;/li&gt;&#xA;&lt;li&gt;La réservation est bien ajoutée à l&amp;rsquo;historique telle quelle.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h3 id=&#34;tests-associés&#34;&gt;Tests associés&lt;a class=&#34;anchor&#34; href=&#34;#tests-associ%c3%a9s&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Test&lt;/th&gt;&#xA;          &lt;th&gt;Manière&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;Appointment - Server accepts empty date when client bypass applied&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Retire l&amp;rsquo;attribut &lt;code&gt;required&lt;/code&gt;, envoie avec date de réservation vide → s&amp;rsquo;attend à être rejeté par le système (FAIL)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;Appointment - Past date booking accepted&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Envoie une demande de RDV pour hier → s&amp;rsquo;attend à être rejeté par le système (FAIL)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;Les deux tests &lt;strong&gt;sont intentionnellement en échec&lt;/strong&gt;.&lt;br&gt;&#xA;Ils documentent le gap.&lt;/p&gt;</description>
    </item>
    <item>
      <title>09.03.06 — Skills Author</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/03-skills/06-author/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/03-skills/06-author/</guid>
      <description>&lt;h1 id=&#34;090306skills-author&#34;&gt;09.03.06 — Skills Author&lt;a class=&#34;anchor&#34; href=&#34;#090306skills-author&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Skills qui &lt;strong&gt;produisent un livrable&lt;/strong&gt;. Tests, sondes, docs, rapports.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;listing-potentiellement-non-exhaustif&#34;&gt;Listing (potentiellement non exhaustif)&lt;a class=&#34;anchor&#34; href=&#34;#listing-potentiellement-non-exhaustif&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Skill&lt;/th&gt;&#xA;          &lt;th&gt;Cible&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;empiricism&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Vérifier avant d&amp;rsquo;encoder ; ne pas écraser un test gap en échec intentionnel&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;write-a-probe&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Script de &amp;ldquo;sonde&amp;rdquo; jetable, gitignored&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;write-test-strategy&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Génère le document de stratégie de test à partir de la suite&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;extend-coverage&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Étend la couverture à partir du patrimoine existant&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;update-frd-and-tests&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Propage une mise à jour de spec&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;manual-reproduction-guide&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Rédige un scénario de reproduction exécutable par un humain&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;manage-backlog&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;BACKLOG.md&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;pr-report&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Rapport de PR adapté&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;plan-test-effort&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Chiffrage de l&amp;rsquo;effort de test, première passe : exigences graduées, registre de risques allégé, poids relatifs (S / M / L), questions ouvertes&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;empiricism&#34;&gt;&lt;code&gt;empiricism&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#empiricism&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;input  : intention de l&amp;#39;utilisateur (« écris un test qui assert X »)&#xA;output : avant d&amp;#39;encoder X :&#xA;            1. vérifier empiriquement que X est vrai&#xA;                - sonde&#xA;                - gh api du code source&#xA;                - curl -v pour inspecter réponses HTTP&#xA;                - ...&#xA;            2. si X est confirmé → encoder&#xA;            3. si X est faux → corriger l&amp;#39;intention&#xA;            4. si X est ambigu → demander à l&amp;#39;humain&lt;/code&gt;&lt;/pre&gt;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;« &lt;em&gt;Juste remarque, je suppose. Je vérifie empiriquement.&lt;/em&gt; »&lt;/p&gt;</description>
    </item>
    <item>
      <title>09.06 — Ressources publiques</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/06-public-resources/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/06-public-resources/</guid>
      <description>&lt;h1 id=&#34;0906ressources-publiques&#34;&gt;09.06 — Ressources publiques&lt;a class=&#34;anchor&#34; href=&#34;#0906ressources-publiques&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Tableau complet des URLs publiques exposées par le Holy Book. Pour les humains, les LLMs, les scrapers.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;listing-potentiellement-non-exhaustif&#34;&gt;Listing (potentiellement non exhaustif)&lt;a class=&#34;anchor&#34; href=&#34;#listing-potentiellement-non-exhaustif&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;URL&lt;/th&gt;&#xA;          &lt;th&gt;Contenu&lt;/th&gt;&#xA;          &lt;th&gt;Public cible&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;https://mojo-molotov.github.io/ocarina-holy-book/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Page d&amp;rsquo;accueil (EN)&lt;/td&gt;&#xA;          &lt;td&gt;Humain&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;https://mojo-molotov.github.io/ocarina-holy-book/fr/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Page d&amp;rsquo;accueil (FR)&lt;/td&gt;&#xA;          &lt;td&gt;Humain&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;https://mojo-molotov.github.io/ocarina-holy-book/ru/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Page d&amp;rsquo;accueil (RU)&lt;/td&gt;&#xA;          &lt;td&gt;Humain&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;https://mojo-molotov.github.io/ocarina-holy-book/what-is-it&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Article (EN)&lt;/td&gt;&#xA;          &lt;td&gt;Humain&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;https://mojo-molotov.github.io/ocarina-holy-book/fr/what-is-it&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Article (FR)&lt;/td&gt;&#xA;          &lt;td&gt;Humain&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;https://mojo-molotov.github.io/ocarina-holy-book/ru/what-is-it&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Article (RU)&lt;/td&gt;&#xA;          &lt;td&gt;Humain&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&amp;hellip; (autres pages doc en EN+FR+RU)&lt;/td&gt;&#xA;          &lt;td&gt;&amp;hellip;&lt;/td&gt;&#xA;          &lt;td&gt;Humain&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;https://mojo-molotov.github.io/ocarina-holy-book/llms.txt&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Index minimaliste pour LLMs&lt;/td&gt;&#xA;          &lt;td&gt;LLM&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;https://mojo-molotov.github.io/ocarina-holy-book/llms-full.txt&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Tout le contenu en plaintext&lt;/td&gt;&#xA;          &lt;td&gt;LLM, scraper&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;https://mojo-molotov.github.io/ocarina-holy-book/CLAUDE.md&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Règles projet complètes&lt;/td&gt;&#xA;          &lt;td&gt;LLM, humain (onboarding)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;https://mojo-molotov.github.io/ocarina-holy-book/CLAUDE.slim.md&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Règles projet courtes&lt;/td&gt;&#xA;          &lt;td&gt;LLM (context budget)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;https://mojo-molotov.github.io/ocarina-holy-book/ocarina-en.pdf&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Doc en PDF A4 (EN)&lt;/td&gt;&#xA;          &lt;td&gt;Humain (offline)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;https://mojo-molotov.github.io/ocarina-holy-book/ocarina-fr.pdf&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Doc en PDF A4 (FR)&lt;/td&gt;&#xA;          &lt;td&gt;Humain (offline)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;https://mojo-molotov.github.io/ocarina-holy-book/ocarina-ru.pdf&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Doc en PDF A4 (RU)&lt;/td&gt;&#xA;          &lt;td&gt;Humain (offline)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;https://mojo-molotov.github.io/ocarina-holy-book/skills/&amp;lt;name&amp;gt;&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Page d&amp;rsquo;un skill IA&lt;/td&gt;&#xA;          &lt;td&gt;Humain, LLM&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;https://mojo-molotov.github.io/ocarina-holy-book/assets/content/...&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Images, sons (covers d&amp;rsquo;articles, illustrations)&lt;/td&gt;&#xA;          &lt;td&gt;Humain (rendu inline)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;llmstxt-standard-émergent&#34;&gt;&lt;code&gt;llms.txt&lt;/code&gt; (standard émergent)&lt;a class=&#34;anchor&#34; href=&#34;#llmstxt-standard-%c3%a9mergent&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Spec : &lt;a href=&#34;https://llmstxt.org/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;https://llmstxt.org/&lt;/a&gt; (proposé en 2024).&lt;/p&gt;</description>
    </item>
    <item>
      <title>10.06 — Workflow ocarina-holy-book</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/06-holy-book-workflow/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/06-holy-book-workflow/</guid>
      <description>&lt;h1 id=&#34;1006workflow-ocarina-holy-book&#34;&gt;10.06 — Workflow &lt;code&gt;ocarina-holy-book&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#1006workflow-ocarina-holy-book&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Un seul workflow : &lt;code&gt;deploy.yml&lt;/code&gt;. Sur push main → build VitePress → upload Pages.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;code&#34;&gt;Code&lt;a class=&#34;anchor&#34; href=&#34;#code&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Deploy Ocarina Holy Book&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;on&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;push&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;branches&lt;/span&gt;: [main]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;pull_request&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;branches&lt;/span&gt;: [main]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;workflow_dispatch&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;env&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;TZ&lt;/span&gt;: Europe/Paris&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;permissions&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;contents&lt;/span&gt;: read&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;pages&lt;/span&gt;: write&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;id-token&lt;/span&gt;: write&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;concurrency&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;group&lt;/span&gt;: pages&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;cancel-in-progress&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;jobs&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;build&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;runs-on&lt;/span&gt;: ubuntu-latest&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;steps&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: actions/checkout@v6&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;fetch-depth&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#6272a4&#34;&gt;# ← full clone&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: pnpm/action-setup@fcf821c621167805dd63a29662bd7cb5676c81a8&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Install pnpm&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;version&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;11&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Use Node.js&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: actions/setup-node@v6&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;node-version&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;24&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;cache&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;pnpm&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Install dependencies&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;: pnpm install --frozen-lockfile&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Build&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;: pnpm build&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: actions/upload-pages-artifact@v4&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;path&lt;/span&gt;: docs/.vitepress/dist&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;deploy&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;needs&lt;/span&gt;: build&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;runs-on&lt;/span&gt;: ubuntu-latest&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;environment&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: github-pages&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;url&lt;/span&gt;: ${{ steps.deployment.outputs.page_url }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;steps&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;: deployment&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: actions/deploy-pages@v5&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;analyse&#34;&gt;Analyse&lt;a class=&#34;anchor&#34; href=&#34;#analyse&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;1-tz-europeparis&#34;&gt;1. &lt;code&gt;TZ: Europe/Paris&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#1-tz-europeparis&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;env&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;TZ&lt;/span&gt;: Europe/Paris&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Force la timezone du runner à Europe/Paris.&lt;br&gt;&#xA;Les dates rendues par VitePress (« &lt;em&gt;Last update: 18/05/2026 11h42&lt;/em&gt; ») sont à l&amp;rsquo;heure de Paris, pas en UTC.&lt;/p&gt;</description>
    </item>
    <item>
      <title>12.06 — Yung Innanet (kayos) &#43; VX Underground</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/06-yung-innanet-vxug/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/06-yung-innanet-vxug/</guid>
      <description>&lt;h1 id=&#34;1206yung-innanet-kayos--vx-underground&#34;&gt;12.06 — Yung Innanet (&lt;code&gt;kayos&lt;/code&gt;) + VX Underground&lt;a class=&#34;anchor&#34; href=&#34;#1206yung-innanet-kayos--vx-underground&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Le passage le plus violent du Holy Book reprend des paroles de &lt;strong&gt;&lt;code&gt;true colors&lt;/code&gt;&lt;/strong&gt;, morceau de &lt;strong&gt;Yung Innanet&lt;/strong&gt; (alias &lt;strong&gt;kayos&lt;/strong&gt;), sysadmin originel de &lt;strong&gt;VX Underground&lt;/strong&gt;. Yung Innanet est mort le &lt;strong&gt;18 octobre 2025&lt;/strong&gt; d&amp;rsquo;un suicide à l&amp;rsquo;arme à feu.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;1-identité&#34;&gt;1. Identité&lt;a class=&#34;anchor&#34; href=&#34;#1-identit%c3%a9&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Champ&lt;/th&gt;&#xA;          &lt;th&gt;Valeur&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Alias musical&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Yung Innanet&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Pseudonyme tech&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;kayos&lt;/strong&gt; (prononcé /kɛjəʊs/)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Twitter&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://x.com/yunginnanet&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;@yunginnanet&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;GitHub&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://github.com/yunginnanet/yunginnanet&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;yunginnanet/yunginnanet&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Rôle reconnu&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Sysadmin originel de VX Underground&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Décès&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;18 octobre 2025&lt;/strong&gt;, suicide à l&amp;rsquo;arme à feu&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Profil Last.fm&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://www.last.fm/music/Yung&amp;#43;Innanet/&amp;#43;wiki&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Yung Innanet — Last.fm&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;SoundCloud&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;vxug.fakedoma.in&lt;/em&gt; — &lt;a href=&#34;https://soundcloud.com/queed-inc/vxugfakedomain-vx-underground&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;SoundCloud&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;L&amp;rsquo;information sur le décès n&amp;rsquo;a pas fait la une des médias mainstream, ce qui est cohérent avec le profil &lt;em&gt;underground&lt;/em&gt; de Yung Innanet. Elle est néanmoins &lt;strong&gt;publique au sein de sa scène&lt;/strong&gt; : confirmée notamment par des tweets de &lt;strong&gt;YTCracker&lt;/strong&gt; et de la &lt;strong&gt;veuve&lt;/strong&gt; de Yung Innanet. Il n&amp;rsquo;y a pas matière à épiloguer, cette mention existe uniquement pour expliquer une citation du Holy Book qui n&amp;rsquo;aurait pas de sens sans elle.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.03.07 — match_page / when</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/07-match-page-when/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/07-match-page-when/</guid>
      <description>&lt;h1 id=&#34;020307match_page-when&#34;&gt;02.03.07 — &lt;code&gt;match_page&lt;/code&gt; / &lt;code&gt;when&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#020307match_page-when&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing_with_railway/match_page.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/testing_with_railway/match_page.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Ajouté &lt;strong&gt;après coup&lt;/strong&gt; au framework. Le Holy Book précise : « &lt;em&gt;&lt;code&gt;match_page&lt;/code&gt; et &lt;code&gt;when&lt;/code&gt; ont été ajoutés après coup, l&amp;rsquo;Igoristan était tellement aléatoire que le cas d&amp;rsquo;usage s&amp;rsquo;est imposé de lui-même. Leur implémentation a été simple, preuve de la flexibilité de la grammaire : d&amp;rsquo;autres structures analogues pourraient très bien suivre.&lt;/em&gt; »&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;le-problème&#34;&gt;Le problème&lt;a class=&#34;anchor&#34; href=&#34;#le-probl%c3%a8me&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Certaines pages d&amp;rsquo;une application peuvent être rendues différemment :&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.05.07 — TestCycle[Driver] &#43; modes</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/05-orchestration/07-test-cycle-modes/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/05-orchestration/07-test-cycle-modes/</guid>
      <description>&lt;h1 id=&#34;020507testcycledriver--modes&#34;&gt;02.05.07 — &lt;code&gt;TestCycle[Driver]&lt;/code&gt; + modes&lt;a class=&#34;anchor&#34; href=&#34;#020507testcycledriver--modes&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing/oc_test_cycle.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/testing/oc_test_cycle.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Responsabilité unique&lt;/strong&gt; : orchestrer les campagnes &lt;em&gt;smoke&lt;/em&gt; puis &lt;em&gt;main&lt;/em&gt;, et appliquer un mode de gestion d&amp;rsquo;échec des smoke.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;deux-modes&#34;&gt;Deux modes&lt;a class=&#34;anchor&#34; href=&#34;#deux-modes&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; Mode &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Literal[&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;fail-fast-on-first-smoke-campaigns-sequence-fail&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;wait-for-all-smoke-tests&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Mode&lt;/th&gt;&#xA;          &lt;th&gt;Comportement&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;fail-fast-on-first-smoke-campaigns-sequence-fail&lt;/code&gt; (par défaut)&lt;/td&gt;&#xA;          &lt;td&gt;Dès qu&amp;rsquo;une campagne de smoke fail, les &lt;strong&gt;suivantes&lt;/strong&gt; sont &lt;em&gt;skippées&lt;/em&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;wait-for-all-smoke-tests&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Toutes&lt;/strong&gt; les campagnes de smoke s&amp;rsquo;exécutent. Si &lt;em&gt;au moins une&lt;/em&gt; fail, les campagnes &lt;em&gt;main&lt;/em&gt; sont skippées.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Cas&lt;/th&gt;&#xA;          &lt;th&gt;Mode&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Smoke par dépendance (par exemple : &amp;ldquo;login&amp;rdquo; puis &amp;ldquo;dashboard accessible&amp;rdquo;)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;fail-fast&lt;/code&gt; (si login échoue, dashboard est inutile)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Smoke parallèles (par exemple : &amp;ldquo;homepage&amp;rdquo; + &amp;ldquo;API ping&amp;rdquo;)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;wait-for-all&lt;/code&gt; (on veut voir les deux)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;Le default est &lt;code&gt;fail-fast&lt;/code&gt; parce que c&amp;rsquo;est le cas le plus fréquent.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.07 — Watcher[Driver]</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/07-watcher/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/07-watcher/</guid>
      <description>&lt;h1 id=&#34;0207watcherdriver&#34;&gt;02.07 — &lt;code&gt;Watcher[Driver]&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0207watcherdriver&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing/watcher.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/testing/watcher.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Observateur parallélisé qui tourne en &lt;em&gt;daemon thread&lt;/em&gt; aux côtés de la &lt;code&gt;test_chain&lt;/code&gt;. Conçu pour détecter les &lt;strong&gt;frictions imprévisibles et sans impact direct sur le scénario&lt;/strong&gt; qui se produisent &lt;em&gt;pendant&lt;/em&gt; que le scénario tourne : toasts d&amp;rsquo;erreur aléatoires, validations parasites, popups inattendus.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;le-problème&#34;&gt;Le problème&lt;a class=&#34;anchor&#34; href=&#34;#le-probl%c3%a8me&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Citation du Holy Book (&lt;em&gt;handling-flakiness&lt;/em&gt;) :&lt;/p&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Plus surprenant encore : des applications affichant des toasts d&amp;rsquo;erreur sans raison apparente, ou des formulaires signalant des erreurs de validation sur des saisies pourtant correctes, sans pour autant bloquer le parcours.&lt;/p&gt;</description>
    </item>
    <item>
      <title>03.07 — Unions discriminées &#43; TypeGuard &#43; @final = unions « sealed »</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/03-functional/07-discriminated-unions-typeguards/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/03-functional/07-discriminated-unions-typeguards/</guid>
      <description>&lt;h1 id=&#34;0307unions-discriminées--typeguard--final--unions-sealed&#34;&gt;03.07 — Unions discriminées + TypeGuard + @final = unions « &lt;em&gt;sealed&lt;/em&gt; »&lt;a class=&#34;anchor&#34; href=&#34;#0307unions-discrimin%c3%a9es--typeguard--final--unions-sealed&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Le combo qui rend &lt;code&gt;Result[T]&lt;/code&gt; et &lt;code&gt;TestResult&lt;/code&gt; à la fois &lt;strong&gt;typés&lt;/strong&gt; et &lt;strong&gt;utilisables sans cast&lt;/strong&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;union-discriminée&#34;&gt;Union discriminée&lt;a class=&#34;anchor&#34; href=&#34;#union-discrimin%c3%a9e&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@final&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@dataclass(frozen&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;True&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;Ok&lt;/span&gt;[T](_BaseResult):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    value: T&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    error: &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@final&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@dataclass(frozen&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;True&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;Fail&lt;/span&gt;(_BaseResult):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    error: Exception &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; field(default_factory&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;lambda&lt;/span&gt;: Exception(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Unknown error&amp;#34;&lt;/span&gt;))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; Result[T] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Ok[T] &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; Fail&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;&#xA;&lt;li&gt;Deux constructeurs : &lt;code&gt;Ok[T]&lt;/code&gt; et &lt;code&gt;Fail&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Le &lt;em&gt;discriminant&lt;/em&gt; est l&amp;rsquo;&lt;em&gt;isinstance check&lt;/em&gt; (pas un champ comme &lt;code&gt;tag: Literal[&amp;quot;ok&amp;quot;, &amp;quot;fail&amp;quot;]&lt;/code&gt;).&lt;/li&gt;&#xA;&lt;li&gt;Les deux sont &lt;code&gt;@final&lt;/code&gt; : &lt;em&gt;aucun&lt;/em&gt; sous-type possible. → l&amp;rsquo;union est &lt;strong&gt;exhaustive&lt;/strong&gt;, donc « &lt;em&gt;sealed&lt;/em&gt; ».&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;narrowing-typeguard&#34;&gt;Narrowing, &lt;code&gt;TypeGuard&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#narrowing-typeguard&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;is_ok&lt;/span&gt;[T](result: Result[T]) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; TypeGuard[Ok[T]]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;isinstance&lt;/span&gt;(result, Ok)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;is_fail&lt;/span&gt;[T](result: Result[T]) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; TypeGuard[Fail]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;isinstance&lt;/span&gt;(result, Fail)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;handle_result&lt;/span&gt;(result: Result[&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;]) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; is_ok(result):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Got: &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;result&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;value&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;          &lt;span style=&#34;color:#6272a4&#34;&gt;# mypy : result est Ok[int], donc .value existe&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; is_fail(result):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Error: &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;result&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;error&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;        &lt;span style=&#34;color:#6272a4&#34;&gt;# mypy : result est Fail, donc .error existe&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#6272a4&#34;&gt;# mypy peut détecter que cette branche est inatteignable (exhaustivité)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;_baseresult&#34;&gt;&lt;code&gt;_BaseResult&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#_baseresult&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_BaseResult&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    error: Exception &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Cette base permet d&amp;rsquo;accéder à &lt;code&gt;result.error&lt;/code&gt; &lt;strong&gt;sans narrower&lt;/strong&gt; :&lt;/p&gt;</description>
    </item>
    <item>
      <title>04.07 — Politique de couverture : ce qui est testé, ce qui ne l&#39;est pas, pourquoi</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/07-coverage-policy/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/07-coverage-policy/</guid>
      <description>&lt;h1 id=&#34;0407politique-de-couverture-ce-qui-est-testé-ce-qui-ne-lest-pas-pourquoi&#34;&gt;04.07 — Politique de couverture : ce qui est testé, ce qui ne l&amp;rsquo;est pas, pourquoi&lt;a class=&#34;anchor&#34; href=&#34;#0407politique-de-couverture-ce-qui-est-test%c3%a9-ce-qui-ne-lest-pas-pourquoi&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;La couverture par ligne est &lt;strong&gt;explicitement scopée&lt;/strong&gt; au DSL pur et à l&amp;rsquo;infra agnostique. Le reste est testé par d&amp;rsquo;autres moyens (cram, types, snapshots, e2e) ou hors scope (shapes inertes).&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;pyprojecttomltoolcoveragerun&#34;&gt;&lt;code&gt;pyproject.toml#tool.coverage.run&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#pyprojecttomltoolcoveragerun&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[tool.coverage.run]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;omit = [&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;*/__init__.py&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;*/tests/*&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;*/test_*.py&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/ocarina/custom_errors/*&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/ocarina/custom_types/*&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/ocarina/ports/*&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/ocarina/opinionated/loggers/custom_types/*&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/**/*singleton.py&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/**/consts/**&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#6272a4&#34;&gt;# Selenium adapter layer — exercised only against a real browser.&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/ocarina/infra/selenium/*&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/ocarina/dsl/testing/selenium/*&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/ocarina/opinionated/cli/selenium/*&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/ocarina/pom/selenium/muted.py&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#6272a4&#34;&gt;# Playwright adapter layer — exercised only against a real browser.&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/ocarina/infra/playwright/*&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/ocarina/dsl/testing/playwright/*&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/ocarina/opinionated/cli/playwright/*&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/ocarina/pom/playwright/*&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/ocarina/opinionated/cli/phantoms.py&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#6272a4&#34;&gt;# Opinionated loggers — only file_logger.py stays in scope.&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/ocarina/opinionated/loggers/create_matching_logger.py&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/ocarina/opinionated/loggers/muted_logger.py&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/ocarina/opinionated/loggers/print_logger.py&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/ocarina/opinionated/loggers/print_and_file_logger.py&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/ocarina/opinionated/loggers/utils/*&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#6272a4&#34;&gt;# Traversed by cram tests&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/ocarina/opinionated/cli/store.py&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/ocarina/opinionated/cli/builder.py&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;catégories&#34;&gt;Catégories&lt;a class=&#34;anchor&#34; href=&#34;#cat%c3%a9gories&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;1-exclusions-évidentes&#34;&gt;1. Exclusions évidentes&lt;a class=&#34;anchor&#34; href=&#34;#1-exclusions-%c3%a9videntes&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Pattern&lt;/th&gt;&#xA;          &lt;th&gt;Justification&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;*/__init__.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Modules vides (Python 3 n&amp;rsquo;en a souvent pas besoin)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;*/tests/*&lt;/code&gt;, &lt;code&gt;*/test_*.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Les tests ne se testent pas eux-mêmes&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h3 id=&#34;2-types-errors-ports-shapes&#34;&gt;2. Types / errors / ports / shapes&lt;a class=&#34;anchor&#34; href=&#34;#2-types-errors-ports-shapes&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Chemin&lt;/th&gt;&#xA;          &lt;th&gt;Justification&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/custom_types/*&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Juste des &lt;code&gt;type X = ...&lt;/code&gt; et frozen dataclasses. Pas de logique runtime.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/custom_errors/*&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Juste des sous-classes d&amp;rsquo;Exception. Pas de logique.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/ports/*&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;ABCs et Protocols. Pas de comportement.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/opinionated/loggers/custom_types/*&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;id.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/**/consts/**&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Constantes (&lt;code&gt;LOGGERS_CHOICES = (...)&lt;/code&gt;).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/**/*singleton.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Wrappers Singleton triviaux.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h3 id=&#34;3-couches-selenium-et-playwright-e2e-seulement&#34;&gt;3. Couches Selenium et Playwright (e2e seulement)&lt;a class=&#34;anchor&#34; href=&#34;#3-couches-selenium-et-playwright-e2e-seulement&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Chemin&lt;/th&gt;&#xA;          &lt;th&gt;Justification&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/infra/selenium/*&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Code Selenium réel (&lt;code&gt;Chrome()&lt;/code&gt;, &lt;code&gt;Firefox()&lt;/code&gt;). Ne s&amp;rsquo;exécute qu&amp;rsquo;avec un vrai navigateur.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/dsl/testing/selenium/*&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;create_selenium_test&lt;/code&gt;, &lt;code&gt;create_selenium_watcher&lt;/code&gt; (factory triviales sur &lt;code&gt;Test&lt;/code&gt; / &lt;code&gt;Watcher&lt;/code&gt;).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/opinionated/cli/selenium/*&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;create_selenium_*_cli_store&lt;/code&gt; (lit &lt;code&gt;platform.system&lt;/code&gt;, instancie).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/pom/selenium/muted.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;MutedPOM&lt;/code&gt; utilitaire (presque vide).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/infra/playwright/*&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Code Playwright réel + l&amp;rsquo;&lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/06-playwright-actor/&#34;&gt;acteur &lt;code&gt;PlaywrightDriver&lt;/code&gt;&lt;/a&gt;. Le test de bout en bout ne se prouve qu&amp;rsquo;avec un vrai navigateur.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/dsl/testing/playwright/*&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;create_playwright_test&lt;/code&gt;, &lt;code&gt;create_playwright_watcher&lt;/code&gt; (mêmes factory triviales).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/opinionated/cli/playwright/*&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;create_playwright_cli_store&lt;/code&gt; (CLI Playwright).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/pom/playwright/*&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;MutedPlaywrightPOM&lt;/code&gt; + &lt;code&gt;mixins&lt;/code&gt; (Null Object, presque vide).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;Ces fichiers sont &lt;strong&gt;couverts&lt;/strong&gt; par :&lt;/p&gt;</description>
    </item>
    <item>
      <title>05.07 — CI/CD de l&#39;Igoristan</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/05-igoristan/07-ci-deploy/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/05-igoristan/07-ci-deploy/</guid>
      <description>&lt;h1 id=&#34;0507cicd-de-ligoristan&#34;&gt;05.07 — CI/CD de l&amp;rsquo;Igoristan&lt;a class=&#34;anchor&#34; href=&#34;#0507cicd-de-ligoristan&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Deux workflows : &lt;code&gt;ci-pr.yml&lt;/code&gt; sur PR (3 jobs parallèles), &lt;code&gt;deploy.yml&lt;/code&gt; sur push main (GitHub Pages).&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;ci-pryml&#34;&gt;&lt;code&gt;ci-pr.yml&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#ci-pryml&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: CI/PR&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;on&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;pull_request&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;branches&lt;/span&gt;: [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;**&amp;#34;&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;workflow_dispatch&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;defaults&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;shell&lt;/span&gt;: bash&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;jobs&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;format-check&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;runs-on&lt;/span&gt;: ubuntu-latest&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;env&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;WIREIT_CACHE&lt;/span&gt;: github&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;steps&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: actions/checkout@v6&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: pnpm/action-setup@v5&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt;: { &lt;span style=&#34;color:#ff79c6&#34;&gt;version&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;11&lt;/span&gt; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: actions/setup-node@v6&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt;: { &lt;span style=&#34;color:#ff79c6&#34;&gt;node-version: 24, cache&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;pnpm&amp;#34;&lt;/span&gt; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: google/wireit@setup-github-actions-caching/v2&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Install project&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;: make install&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Formatcheck&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;: make ci:format-check&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;lint&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;runs-on&lt;/span&gt;: ubuntu-latest&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;env&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;WIREIT_CACHE&lt;/span&gt;: github&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;steps&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#6272a4&#34;&gt;# ... idem ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: actions/cache@v5&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;path&lt;/span&gt;: .eslintcache&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;key&lt;/span&gt;: eslint-cache-${{ hashFiles(&amp;#39;eslint.config.ts&amp;#39;, &amp;#39;src/**/*.*&amp;#39;, &amp;#39;package.json&amp;#39;) }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Lint&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;: pnpm lint&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;typecheck&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;runs-on&lt;/span&gt;: ubuntu-latest&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;env&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;WIREIT_CACHE&lt;/span&gt;: github&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;steps&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#6272a4&#34;&gt;# ... idem ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Typecheck&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;: pnpm typecheck&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Job&lt;/th&gt;&#xA;          &lt;th&gt;Commande&lt;/th&gt;&#xA;          &lt;th&gt;Cache&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;format-check&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;pnpm exec prettier . --check&lt;/code&gt; (via wireit)&lt;/td&gt;&#xA;          &lt;td&gt;wireit + node_modules&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;lint&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;pnpm exec eslint ... --max-warnings 0 --cache&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;wireit + &lt;code&gt;.eslintcache&lt;/code&gt; (actions/cache)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;typecheck&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;tsc --build --pretty tsconfig.json&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;wireit + &lt;code&gt;tsbuildinfo&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;                          PR opened / push&#xA;                                  │&#xA;                ┌─────────────────┼─────────────────┐&#xA;                ▼                 ▼                 ▼&#xA;        ┌──────────────┐ ┌──────────────┐ ┌──────────────┐&#xA;        │ format-check │ │     lint     │ │  typecheck   │&#xA;        │ (1-2 minutes)│ │ (1-2 minutes)│ │ (1-2 minutes)│&#xA;        └──────────────┘ └──────────────┘ └──────────────┘&#xA;                │                 │                 │&#xA;                └─────────────────┴─────────────────┘&#xA;                                  ▼&#xA;                             PR mergeable&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;deployyml&#34;&gt;&lt;code&gt;deploy.yml&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#deployyml&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Deploy to GitHub Pages&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;on&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;push&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;branches&lt;/span&gt;: [main]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;workflow_dispatch&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;permissions&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;contents&lt;/span&gt;: read&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;pages&lt;/span&gt;: write&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;id-token&lt;/span&gt;: write&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;concurrency&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;group&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;pages&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;cancel-in-progress&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;jobs&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;build&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;runs-on&lt;/span&gt;: ubuntu-latest&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;steps&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: actions/checkout@v6&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: pnpm/action-setup@v5&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt;: { &lt;span style=&#34;color:#ff79c6&#34;&gt;version&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;11&lt;/span&gt; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: actions/setup-node@v6&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt;: { &lt;span style=&#34;color:#ff79c6&#34;&gt;node-version: 24, cache&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;pnpm&amp;#34;&lt;/span&gt; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Install dependencies&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;: pnpm install --frozen-lockfile&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Build&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;: pnpm build&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Upload Pages artifact&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: actions/upload-pages-artifact@v4&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt;: { &lt;span style=&#34;color:#ff79c6&#34;&gt;path&lt;/span&gt;: dist/client }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;deploy&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;needs&lt;/span&gt;: build&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;runs-on&lt;/span&gt;: ubuntu-latest&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;environment&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: github-pages&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;url&lt;/span&gt;: ${{ steps.deployment.outputs.page_url }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;steps&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;: deployment&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: actions/deploy-pages@v5&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Job&lt;/th&gt;&#xA;          &lt;th&gt;Effet&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;build&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;pnpm install --frozen-lockfile&lt;/code&gt; → &lt;code&gt;pnpm build&lt;/code&gt; (= &lt;code&gt;wireit build&lt;/code&gt; = &lt;code&gt;lint → typecheck → vike build&lt;/code&gt;) → upload &lt;code&gt;dist/client&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;deploy&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;actions/deploy-pages@v5&lt;/code&gt; → publie l&amp;rsquo;artifact → URL = &lt;a href=&#34;https://mojo-molotov.github.io/igoristan/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;https://mojo-molotov.github.io/igoristan/&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;concurrency-pages--cancel-in-progress-false&#34;&gt;&lt;code&gt;concurrency: pages&lt;/code&gt; + &lt;code&gt;cancel-in-progress: false&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#concurrency-pages--cancel-in-progress-false&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;concurrency&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;group&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;pages&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;cancel-in-progress&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;false&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Empêche &lt;strong&gt;deux déploiements simultanés&lt;/strong&gt; (race condition).&lt;br&gt;&#xA;Le second attend (&lt;code&gt;cancel-in-progress: false&lt;/code&gt; → on &lt;em&gt;ne&lt;/em&gt; cancel &lt;em&gt;pas&lt;/em&gt; le run en cours).&lt;/p&gt;</description>
    </item>
    <item>
      <title>06.07 — Flux de coordination OTP &#43; l&#39;anti-précision volontaire</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/06-tests-workers/07-otp-coordination-flow/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/06-tests-workers/07-otp-coordination-flow/</guid>
      <description>&lt;h1 id=&#34;0607flux-de-coordination-otp--limprécision-volontaire&#34;&gt;06.07 — Flux de coordination OTP + l&amp;rsquo;imprécision volontaire&lt;a class=&#34;anchor&#34; href=&#34;#0607flux-de-coordination-otp--limpr%c3%a9cision-volontaire&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;La raison d&amp;rsquo;être du backend : permettre à N workers parallèles d&amp;rsquo;Ocarina de &lt;strong&gt;récupérer le bon OTP&lt;/strong&gt; pour leur &lt;em&gt;user&lt;/em&gt;, même quand plusieurs OTP sont générés.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;flux&#34;&gt;Flux&lt;a class=&#34;anchor&#34; href=&#34;#flux&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;   ┌───────────────────────────────────────────────────────────────────┐&#xA;   │               Worker (parmi --workers 3 d&amp;#39;Ocarina)                │&#xA;   └───────────────────────────────────────────────────────────────────┘&#xA;                                     │&#xA;                                     ▼&#xA;   ┌───────────────────────────────────────────────────────────────────┐&#xA;   │ Selenium ouvre la page de connexion au Dashboard                  │&#xA;   └─────────────────────────────────┬─────────────────────────────────┘&#xA;                                     ▼&#xA;   ┌───────────────────────────────────────────────────────────────────┐&#xA;   │ Acquisition du lock distribué Redis (OTP_SEND_LOCK_KEY)           │&#xA;   │   → seul ce worker peut cliquer &amp;#34;Send OTP&amp;#34; pendant ACQ            │&#xA;   └─────────────────────────────────┬─────────────────────────────────┘&#xA;                                     ▼&#xA;   ┌───────────────────────────────────────────────────────────────────┐&#xA;   │ min_utc_date = datetime.now(UTC)                                  │&#xA;   └─────────────────────────────────┬─────────────────────────────────┘&#xA;                                     ▼&#xA;   ┌───────────────────────────────────────────────────────────────────┐&#xA;   │ Cache L1 : enregistre min_utc_date + username dans le cache       │&#xA;   │   (clés réservées par reserve_free_cache_key)                     │&#xA;   └─────────────────────────────────┬─────────────────────────────────┘&#xA;                                     ▼&#xA;   ┌───────────────────────────────────────────────────────────────────┐&#xA;   │ Selenium tape username + password + cocher OTP + click &amp;#34;REQ OTP&amp;#34;  │&#xA;   └─────────────────────────────────┬─────────────────────────────────┘&#xA;                                     ▼&#xA;   ┌───────────────────────────────────────────────────────────────────┐&#xA;   │ IGORISTAN UI : fetch /api/otp?_user=&amp;lt;username&amp;gt;                    │&#xA;   │   (x-api-key tapé par Selenium dans l&amp;#39;UI)                         │&#xA;   └─────────────────────────────────┬─────────────────────────────────┘&#xA;                                     ▼&#xA;   ┌───────────────────────────────────────────────────────────────────┐&#xA;   │ TESTS-WORKERS /api/otp :                                          │&#xA;   │   generate(secret) → otpCode                                      │&#xA;   │   createdAt = floor(now/1000)*1000  ← amputation ms               │&#xA;   │   event = { _user, otpCode, createdAt, expiresAt, ... }           │&#xA;   │   redis.set(&amp;#34;otp:&amp;lt;now&amp;gt;:&amp;lt;uuid&amp;gt;&amp;#34;, JSON, EX 360)                     │&#xA;   │   return event                                                    │&#xA;   └──────────────────────────────────┬────────────────────────────────┘&#xA;                                      ▼&#xA;   ┌───────────────────────────────────────────────────────────────────┐&#xA;   │ IGORISTAN UI : affiche écran OTP                                  │&#xA;   └──────────────────────────────────┬────────────────────────────────┘&#xA;                                      ▼&#xA;   ┌───────────────────────────────────────────────────────────────────┐&#xA;   │ Release du lock Redis OTP_SEND_LOCK_KEY                           │&#xA;   └──────────────────────────────────┬────────────────────────────────┘&#xA;                                      ▼&#xA;   ┌───────────────────────────────────────────────────────────────────┐&#xA;   │ Selenium : retrieve_dashboard_otp_code(min_utc_date, _user)       │&#xA;   │   ↓                                                               │&#xA;   │   GET /api/otp-history  (x-api-key = IGOR_API_KEY)                │&#xA;   │   ↓                                                               │&#xA;   │   TESTS-WORKERS : SCAN otp:* + MGET → all events                  │&#xA;   │   ↓                                                               │&#xA;   │   filtre côté client par _user                                    │&#xA;   │   filtre createdAt &amp;gt;= min_utc_date - 1s                           │&#xA;   │   tri ASC sur createdAt                                           │&#xA;   │   return first.otpCode                                            │&#xA;   └──────────────────────────────────┬────────────────────────────────┘&#xA;                                      ▼&#xA;   ┌───────────────────────────────────────────────────────────────────┐&#xA;   │ Selenium tape l&amp;#39;OTP dans l&amp;#39;UI Igoristan                           │&#xA;   │   → AUTHENTICATED_WITH_MFA                                        │&#xA;   └───────────────────────────────────────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;races-conditions&#34;&gt;Races conditions&lt;a class=&#34;anchor&#34; href=&#34;#races-conditions&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Worker A : &lt;code&gt;min_utc_date_A = 13:27:53.123&lt;/code&gt;, génère OTP_A à 13:27:53.250.&lt;/li&gt;&#xA;&lt;li&gt;Worker B : &lt;code&gt;min_utc_date_B = 13:27:53.130&lt;/code&gt;, génère OTP_B à 13:27:53.470.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;Sans coordination, A pourrait récupérer OTP&lt;em&gt;B (au lieu de OTP_A) parce que les timestamps sont _très proches&lt;/em&gt; (et tronqués à la seconde près).&lt;/p&gt;</description>
    </item>
    <item>
      <title>07.07 — HumanizedDriver</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/07-humanized-driver/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/07-humanized-driver/</guid>
      <description>&lt;h1 id=&#34;0707humanizeddriver&#34;&gt;07.07 — &lt;code&gt;HumanizedDriver&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0707humanizeddriver&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina-example/blob/main/src/lib/ext/selenium/humanize/proxy.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/lib/ext/selenium/humanize/proxy.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Proxy pattern : wrappe un &lt;code&gt;WebDriver&lt;/code&gt; Selenium et intercepte uniquement &lt;code&gt;find_element*&lt;/code&gt; pour retourner des &lt;code&gt;WebElement&lt;/code&gt; qui font des &lt;code&gt;send_keys&lt;/code&gt; &lt;em&gt;humanisés&lt;/em&gt; (frappe lente avec typos, hésitations, corrections).&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;code&#34;&gt;Code&lt;a class=&#34;anchor&#34; href=&#34;#code&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;HumanizedDriver&lt;/span&gt;(WebDriver):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;__init__&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, driver: WebDriver, &lt;span style=&#34;color:#ff79c6&#34;&gt;**&lt;/span&gt;keyboard_config: Unpack[KeyboardConfig]) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;object&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;__init__&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_driver &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; driver&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_config &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; keyboard_config&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;find_element&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, by: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; RelativeBy &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;id&amp;#34;&lt;/span&gt;, value: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; _HumanizedWebElement:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        element &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_driver&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;find_element(by, value)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; _HumanizedWebElement(element, &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_config)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;find_elements&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, by: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; RelativeBy &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;id&amp;#34;&lt;/span&gt;, value: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;list&lt;/span&gt;[WebElement]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        elements &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_driver&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;find_elements(by, value)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; [_HumanizedWebElement(el, &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_config) &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; el &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; elements]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;__getattr__&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, name: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;getattr&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_driver, name)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;1-hérite-de-webdriver&#34;&gt;1. Hérite de &lt;code&gt;WebDriver&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#1-h%c3%a9rite-de-webdriver&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;HumanizedDriver&lt;/span&gt;(WebDriver):&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;HumanizedDriver&lt;/code&gt; &lt;em&gt;passe&lt;/em&gt; le &lt;em&gt;type check&lt;/em&gt; &lt;code&gt;isinstance(x, WebDriver)&lt;/code&gt;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>08.07 — Gaps spec (G-SPEC-1 à G-SPEC-3)</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/08-ai-example/07-spec-gaps/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/08-ai-example/07-spec-gaps/</guid>
      <description>&lt;h1 id=&#34;0807gaps-spec-g-spec-1-à-g-spec-3&#34;&gt;08.07 — Gaps spec (G-SPEC-1 à G-SPEC-3)&lt;a class=&#34;anchor&#34; href=&#34;#0807gaps-spec-g-spec-1-%c3%a0-g-spec-3&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Trois gaps sur le comportement métier vs la spec &lt;em&gt;attendue&lt;/em&gt; d&amp;rsquo;un système de santé.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;g-spec-1history-trié-par-ordre-de-soumission-pas-par-date&#34;&gt;G-SPEC-1 — History trié par ordre de soumission, pas par date&lt;a class=&#34;anchor&#34; href=&#34;#g-spec-1history-tri%c3%a9-par-ordre-de-soumission-pas-par-date&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;source&#34;&gt;Source&lt;a class=&#34;anchor&#34; href=&#34;#source&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;// views/page_history.php&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;foreach&lt;/span&gt; (&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$_SESSION&lt;/span&gt;[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;history&amp;#39;&lt;/span&gt;] &lt;span style=&#34;color:#ff79c6&#34;&gt;as&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$appointment&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#6272a4&#34;&gt;// ... render ...&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Pas de &lt;code&gt;usort&lt;/code&gt;. Pas de tri. Aucune étape de tri n&amp;rsquo;existe nulle part.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;appointment.php&lt;/code&gt; push avec &lt;code&gt;array_push($_SESSION[&#39;history&#39;], $_POST)&lt;/code&gt; — append en queue (c&amp;rsquo;est un &lt;em&gt;pushback&lt;/em&gt;).&lt;/p&gt;</description>
    </item>
    <item>
      <title>09.03.07 — Skills Refactor</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/03-skills/07-refactor/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/03-skills/07-refactor/</guid>
      <description>&lt;h1 id=&#34;090307skills-refactor&#34;&gt;09.03.07 — Skills Refactor&lt;a class=&#34;anchor&#34; href=&#34;#090307skills-refactor&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Skills qui &lt;strong&gt;refactor&lt;/strong&gt; la base de tests automatisés existante.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;listing-potentiellement-non-exhaustif&#34;&gt;Listing (potentiellement non exhaustif)&lt;a class=&#34;anchor&#34; href=&#34;#listing-potentiellement-non-exhaustif&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Skill&lt;/th&gt;&#xA;          &lt;th&gt;Cible&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;refactor-fragmentation&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;DRY selon préférence utilisateur&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;introduce-pom-retries&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Retries internes aux POMs, avec dédoublement (first-try + with-retries)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;refactor-fragmentation&#34;&gt;&lt;code&gt;refactor-fragmentation&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#refactor-fragmentation&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;input  : la base de tests&#xA;output : suggestions de refactor DRY :&#xA;            - blocs répétés dans 3+ scénarios → extract en fragment&#xA;            - connectors quasi-identiques → extract un connector paramétré&#xA;            - séquences de log+screenshot répétées → extract un helper&#xA;         + recommandation au cas par cas&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; :&lt;/p&gt;</description>
    </item>
    <item>
      <title>10.07 — tests-workers : pas de CI GitHub, déploiement Vercel auto</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/07-tests-workers-vercel/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/07-tests-workers-vercel/</guid>
      <description>&lt;h1 id=&#34;1007tests-workers-pas-de-ci-github-déploiement-vercel-auto&#34;&gt;10.07 — &lt;code&gt;tests-workers&lt;/code&gt; : pas de CI GitHub, déploiement Vercel auto&lt;a class=&#34;anchor&#34; href=&#34;#1007tests-workers-pas-de-ci-github-d%c3%a9ploiement-vercel-auto&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Le seul dépôt de l&amp;rsquo;écosystème &lt;strong&gt;sans workflow GitHub Actions&lt;/strong&gt;. Tout passe par Vercel.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;le-contenu-du-github&#34;&gt;Le contenu du &lt;code&gt;.github/&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#le-contenu-du-github&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;tests-workers/&#xA;└── (aucun .github/ — pas de workflows)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Pas de &lt;code&gt;ci.yml&lt;/code&gt;. Pas de &lt;code&gt;deploy.yml&lt;/code&gt;. Pas d&amp;rsquo;action GitHub.&lt;/p&gt;&#xA;&lt;h2 id=&#34;verceljson&#34;&gt;&lt;code&gt;vercel.json&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#verceljson&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;version&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;2&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;buildCommand&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;echo &amp;#39;No build required&amp;#39;&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;outputDirectory&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;.&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;framework&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;null&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Vercel &lt;em&gt;détecte&lt;/em&gt; le projet comme &lt;em&gt;TypeScript Edge Functions&lt;/em&gt;, build à la volée (transpile TS au runtime).&lt;/p&gt;</description>
    </item>
    <item>
      <title>12.07 — Le mouvement de fond</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/07-underlying-movement/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/07-underlying-movement/</guid>
      <description>&lt;h1 id=&#34;1207le-mouvement-de-fond&#34;&gt;12.07 — Le mouvement de fond&lt;a class=&#34;anchor&#34; href=&#34;#1207le-mouvement-de-fond&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Trois trajectoires possibles pour qui était dans la scène hackers 1999-2012 : (1) rester &lt;em&gt;black hat&lt;/em&gt; et finir en prison (au mieux), (2) décrocher complètement, (3) &lt;strong&gt;se reconstruire seul par la Recherche&lt;/strong&gt;. Le Holy Book parle de la troisième. C&amp;rsquo;est de là qu&amp;rsquo;Ocarina vient.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;1-le-passage-clé-du-holy-book&#34;&gt;1. Le passage clé du Holy Book&lt;a class=&#34;anchor&#34; href=&#34;#1-le-passage-cl%c3%a9-du-holy-book&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;&lt;em&gt;De l&amp;rsquo;Enfer à là où nous en sommes aujourd&amp;rsquo;hui.&lt;/em&gt;&#xA;&lt;em&gt;Sauvés par la Recherche, par de belles valeurs, par le travail ingrat. Pas celui qui fait briller. Celui transmis par ceux qui savent détecter un potentiel et le sauver avant qu&amp;rsquo;il ne soit trop tard.&lt;/em&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.05.08 — filter_tests_by_ids</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/05-orchestration/08-filter-tests-by-ids/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/05-orchestration/08-filter-tests-by-ids/</guid>
      <description>&lt;h1 id=&#34;020508filter_tests_by_ids&#34;&gt;02.05.08 — &lt;code&gt;filter_tests_by_ids&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#020508filter_tests_by_ids&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing/filter_tests_by_ids.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/testing/filter_tests_by_ids.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Utilisé par &lt;code&gt;TestSuite.__init__&lt;/code&gt; pour appliquer les flags CLI &lt;code&gt;--only &amp;lt;ids&amp;gt;&lt;/code&gt; et &lt;code&gt;--exclude &amp;lt;ids&amp;gt;&lt;/code&gt;. Mutex : on ne peut pas passer les deux.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;code&#34;&gt;Code&lt;a class=&#34;anchor&#34; href=&#34;#code&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;filter_tests_by_ids&lt;/span&gt;[Driver](&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tests: Sequence[Test[Driver]],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    only: Iterable[&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; (),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    exclude: Iterable[&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; (),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logger: ILogger,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; Sequence[Test[Driver]]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    only_set &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;set&lt;/span&gt;(only)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    exclude_set &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;set&lt;/span&gt;(exclude)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; only_set &lt;span style=&#34;color:#ff79c6&#34;&gt;and&lt;/span&gt; exclude_set:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;raise&lt;/span&gt; ValueError(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;--only and --exclude cannot be used together&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    known_ids &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {test&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;test_id &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; test &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; tests}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; only_set:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        matched &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; only_set &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;amp;&lt;/span&gt; known_ids&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; matched:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            joined &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;, &amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;sorted&lt;/span&gt;(matched))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            logger&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;info(&lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;--only: matched test IDs: &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;joined&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; [test &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; test &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; tests &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; test&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;test_id &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; only_set]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; exclude_set:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        matched &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; exclude_set &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;amp;&lt;/span&gt; known_ids&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; matched:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            joined &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;, &amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;sorted&lt;/span&gt;(matched))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            logger&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;info(&lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;--exclude: matched test IDs: &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;joined&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; [test &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; test &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; tests &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; test&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;test_id &lt;span style=&#34;color:#ff79c6&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; exclude_set]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; tests&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;quatre-garanties&#34;&gt;Quatre garanties&lt;a class=&#34;anchor&#34; href=&#34;#quatre-garanties&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;1-mutex---only---exclude&#34;&gt;1. Mutex &lt;code&gt;--only&lt;/code&gt; / &lt;code&gt;--exclude&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#1-mutex---only---exclude&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; only_set &lt;span style=&#34;color:#ff79c6&#34;&gt;and&lt;/span&gt; exclude_set:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;raise&lt;/span&gt; ValueError(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;--only and --exclude cannot be used together&amp;#34;&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tenter de passer les deux est une erreur &lt;em&gt;runtime&lt;/em&gt; (pas une erreur &lt;em&gt;mypy&lt;/em&gt;). Sémantiquement, ça n&amp;rsquo;aurait pas de sens : « &lt;em&gt;ne garder que A, B&lt;/em&gt; » ET « &lt;em&gt;exclure C, D&lt;/em&gt; » ? Qu&amp;rsquo;est-ce que c&amp;rsquo;est que ces putains de conneries (à l&amp;rsquo;échelle d&amp;rsquo;une CLI) ? Le mutex évite l&amp;rsquo;ambiguïté.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.08 — POMBase</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/08-pom-base/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/08-pom-base/</guid>
      <description>&lt;h1 id=&#34;0208pombase&#34;&gt;02.08 — &lt;code&gt;POMBase&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0208pombase&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/pom/base.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/pom/base.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Base abstraite &lt;strong&gt;framework-agnostic&lt;/strong&gt; du Page Object Model. Deux méthodes obligatoires. Aucune mention de Selenium.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;code&#34;&gt;Code&lt;a class=&#34;anchor&#34; href=&#34;#code&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;POMBase&lt;/span&gt;(ABC):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;verify&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, timeout: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;float&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; Self:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;get_current_title&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Six octets de contrat (les deux signatures). C&amp;rsquo;est tout.&lt;/p&gt;&#xA;&lt;h2 id=&#34;pourquoi-deux-méthodes&#34;&gt;Pourquoi deux méthodes&lt;a class=&#34;anchor&#34; href=&#34;#pourquoi-deux-m%c3%a9thodes&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;verifytimeoutnone---self&#34;&gt;&lt;code&gt;verify(timeout=None) -&amp;gt; Self&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#verifytimeoutnone---self&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;But : &lt;strong&gt;prouver qu&amp;rsquo;on est sur la bonne page&lt;/strong&gt;. Sinon, lève.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;L&amp;rsquo;argument &lt;code&gt;timeout&lt;/code&gt; permet d&amp;rsquo;attendre &lt;em&gt;jusqu&amp;rsquo;à un certain temps&lt;/em&gt; que la page se charge (utile pour les Selenium &lt;code&gt;WebDriverWait&lt;/code&gt;).&lt;/li&gt;&#xA;&lt;li&gt;Le default &lt;code&gt;None&lt;/code&gt; laisse l&amp;rsquo;implémentation choisir (typiquement, lire depuis &lt;code&gt;get_timeout()&lt;/code&gt; qui lit la CLI).&lt;/li&gt;&#xA;&lt;li&gt;Le retour &lt;code&gt;Self&lt;/code&gt; permet le &lt;em&gt;method chaining&lt;/em&gt; fluide (&lt;code&gt;MyPage(...).open().verify().click()&lt;/code&gt;).&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;C&amp;rsquo;est l&amp;rsquo;unique méthode dont on a vraiment besoin Test-side pour garantir que « &lt;em&gt;je suis bien sur la page que je veux&lt;/em&gt; ». Toutes les actions (&lt;code&gt;click_xxx&lt;/code&gt;, &lt;code&gt;enter_xxx&lt;/code&gt;) sont laissées à la subclass.&lt;/p&gt;</description>
    </item>
    <item>
      <title>04.08 — Allure &#43; action composite allure-history &#43; déploiement GH Pages</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/08-allure-history/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/08-allure-history/</guid>
      <description>&lt;h1 id=&#34;0408allure--action-composite-allure-history--déploiement-gh-pages&#34;&gt;04.08 — Allure + action composite &lt;code&gt;allure-history&lt;/code&gt; + déploiement GH Pages&lt;a class=&#34;anchor&#34; href=&#34;#0408allure--action-composite-allure-history--d%c3%a9ploiement-gh-pages&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Le rapport Allure est généré à chaque CI, &lt;em&gt;historisé&lt;/em&gt; sur une branche Git dédiée, puis &lt;em&gt;publié&lt;/em&gt; sur GitHub Pages. Live URL : &lt;a href=&#34;https://mojo-molotov.github.io/ocarina/allure-report/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;https://mojo-molotov.github.io/ocarina/allure-report/&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;pipeline&#34;&gt;Pipeline&lt;a class=&#34;anchor&#34; href=&#34;#pipeline&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;┌────────────────────────────────────────────────────────┐&#xA;│ main_ci.yml (push/PR main, ou dispatch)                │&#xA;└──────────────────┬─────────────────────────────────────┘&#xA;                   ▼&#xA;   ┌───────────────────────────────────┐&#xA;   │ job : test                        │&#xA;   │  - matrix (ubuntu / windows × py) │&#xA;   │  - make test (cram + pytest)      │&#xA;   │  - upload-artifact allure-results │&#xA;   └───────────────┬───────────────────┘&#xA;                   ▼&#xA;   ┌───────────────────────────────────┐&#xA;   │ job : allure-history              │&#xA;   │  - download all allure-results    │&#xA;   │  - action composite locale :      │&#xA;   │       allure-history/action.yml   │&#xA;   │  - upload-artifact allure-report  │&#xA;   └───────────────┬───────────────────┘&#xA;                   ▼&#xA;   ┌───────────────────────────────────┐&#xA;   │ job : deploy                      │&#xA;   │  - download allure-report         │&#xA;   │  - prepare pages-root/            │&#xA;   │  - actions/upload-pages-artifact  │&#xA;   │  - actions/deploy-pages           │&#xA;   └───────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;allure-history&#34;&gt;&lt;code&gt;allure-history&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#allure-history&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;.github/actions/allure-history/action.yml&lt;/code&gt; : action composite &lt;strong&gt;locale&lt;/strong&gt; au repo (&lt;code&gt;uses: ./.github/actions/allure-history&lt;/code&gt;).&lt;/p&gt;</description>
    </item>
    <item>
      <title>07.08 — Cache L1 &#43; clés réservées &#43; locks Redis distribués</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/08-caches-locks/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/08-caches-locks/</guid>
      <description>&lt;h1 id=&#34;0708cache-l1--clés-réservées--locks-redis-distribués&#34;&gt;07.08 — Cache L1 + clés réservées + locks Redis distribués&lt;a class=&#34;anchor&#34; href=&#34;#0708cache-l1--cl%c3%a9s-r%c3%a9serv%c3%a9es--locks-redis-distribu%c3%a9s&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Mécanique de coordination interne : un cache in-memory pour partager des valeurs &lt;em&gt;au sein d&amp;rsquo;un test&lt;/em&gt;, un lock Redis pour sérialiser un click &lt;em&gt;entre plusieurs workers&lt;/em&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;cache-l1&#34;&gt;Cache L1&lt;a class=&#34;anchor&#34; href=&#34;#cache-l1&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/caches/l1.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; dogpile.cache &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; make_region&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;in_memory_cache_with_30m_ttl &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; make_region()&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;configure(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;dogpile.cache.memory&amp;#34;&lt;/span&gt;, expiration_time&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#bd93f9&#34;&gt;30&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;60&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Un seul cache, global, &lt;code&gt;dogpile.cache.memory&lt;/code&gt; backend, TTL 30 minutes.&lt;/p&gt;&#xA;&lt;p&gt;Choix « &lt;em&gt;memory&lt;/em&gt; » → tout vit en RAM, pas de Redis pour ce cache &lt;em&gt;local&lt;/em&gt;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>08.08 — BFCache Chrome (B-BROWSER-1) &#43; artefacts d&#39;environnement</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/08-ai-example/08-bfcache/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/08-ai-example/08-bfcache/</guid>
      <description>&lt;h1 id=&#34;0808bfcache-chrome-b-browser-1--artefacts-denvironnement&#34;&gt;08.08 — BFCache Chrome (B-BROWSER-1) + artefacts d&amp;rsquo;environnement&lt;a class=&#34;anchor&#34; href=&#34;#0808bfcache-chrome-b-browser-1--artefacts-denvironnement&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Chrome restore une page &lt;code&gt;no-store&lt;/code&gt; après logout, depuis le BFCache, alors qu&amp;rsquo;il s&amp;rsquo;agit d&amp;rsquo;une page protégée par authentification.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;b-browser-1chrome-nous-emmerde-avec-son-bfcache&#34;&gt;B-BROWSER-1 — Chrome nous emmerde avec son BFCache&lt;a class=&#34;anchor&#34; href=&#34;#b-browser-1chrome-nous-emmerde-avec-son-bfcache&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;symptôme&#34;&gt;Symptôme&lt;a class=&#34;anchor&#34; href=&#34;#sympt%c3%b4me&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;Après déconnexion, cliquer sur le bouton &amp;ldquo;précédent&amp;rdquo; afin d&amp;rsquo;être redirigé vers une page qui était protégée par authentification (par exemple : &lt;code&gt;history.php&lt;/code&gt;) :&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Chrome&lt;/strong&gt; : la page &lt;em&gt;reste affichée&lt;/em&gt; avec son contenu tel qu&amp;rsquo;il est affiché à un utilisateur encore authentifié. L&amp;rsquo;URL reste &lt;code&gt;history.php&lt;/code&gt;, le serveur n&amp;rsquo;est pas recontacté pour valider ou invalider l&amp;rsquo;accès.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Firefox&lt;/strong&gt; : une nouvelle requête est envoyée au serveur, ce dernier voit que la session est morte, il redirige.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;gap-très-ennuyeux&#34;&gt;Gap très ennuyeux&lt;a class=&#34;anchor&#34; href=&#34;#gap-tr%c3%a8s-ennuyeux&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;&lt;code&gt;history.php&lt;/code&gt; envoie correctement :&lt;/p&gt;</description>
    </item>
    <item>
      <title>09.03.08 — Skill State</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/03-skills/08-state/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/03-skills/08-state/</guid>
      <description>&lt;h1 id=&#34;090308skill-state&#34;&gt;09.03.08 — Skill State&lt;a class=&#34;anchor&#34; href=&#34;#090308skill-state&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Un seul skill : &lt;code&gt;question-state&lt;/code&gt;. Interroge l&amp;rsquo;environnement avant de croire un résultat.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;le-skill&#34;&gt;Le skill&lt;a class=&#34;anchor&#34; href=&#34;#le-skill&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;input  : un résultat surprenant (« le test fail »)&#xA;output : une série de questions sur l&amp;#39;état :&#xA;            - sur quel browser tourne-t-on ?&#xA;            - quel commit ?&#xA;            - le SUT est-il warm ou cold ?&#xA;            - le Redis est-il disponible ?&#xA;            - quel est le wait-timeout configuré ?&#xA;            - quelle est la version du driver ?&#xA;            - le ChromeDriver a-t-il son fix password-manager-off ?&#xA;            - est-ce un run isolé ou un run en parallèle ?&#xA;            - est-ce que d&amp;#39;autres tests touchent les mêmes ressources SUT ?&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;approche&#34;&gt;Approche&lt;a class=&#34;anchor&#34; href=&#34;#approche&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; :&lt;/p&gt;</description>
    </item>
    <item>
      <title>12.08 — Ocarina dans l&#39;industrie du test</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/08-ocarina-in-testing-industry/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/08-ocarina-in-testing-industry/</guid>
      <description>&lt;h1 id=&#34;1208ocarina-dans-lindustrie-du-test&#34;&gt;12.08 — Ocarina dans l&amp;rsquo;industrie du test&lt;a class=&#34;anchor&#34; href=&#34;#1208ocarina-dans-lindustrie-du-test&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Ocarina n&amp;rsquo;est pas un &lt;em&gt;meilleur&lt;/em&gt; pytest, pas un &lt;em&gt;meilleur&lt;/em&gt; Robot Framework, pas un &lt;em&gt;meilleur&lt;/em&gt; Cypress. Il est &lt;strong&gt;structurellement ailleurs&lt;/strong&gt;. Ce chapitre essaie d&amp;rsquo;expliquer le &lt;em&gt;shift&lt;/em&gt; qu&amp;rsquo;il représente, et la longueur d&amp;rsquo;avance qu&amp;rsquo;il prend.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;1-la-cartographie-actuelle-de-lindustrie&#34;&gt;1. La cartographie actuelle de l&amp;rsquo;industrie&lt;a class=&#34;anchor&#34; href=&#34;#1-la-cartographie-actuelle-de-lindustrie&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;trois-grandes-familles-doutils-e2e&#34;&gt;Trois grandes familles d&amp;rsquo;outils e2e&lt;a class=&#34;anchor&#34; href=&#34;#trois-grandes-familles-doutils-e2e&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Famille&lt;/th&gt;&#xA;          &lt;th&gt;Exemples&lt;/th&gt;&#xA;          &lt;th&gt;Modèle&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;DSL textuels&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Robot Framework, Cucumber + Gherkin&lt;/td&gt;&#xA;          &lt;td&gt;Couche de traduction permanente entre &lt;em&gt;texte&lt;/em&gt; et &lt;em&gt;code&lt;/em&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Plugins de runner&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;pytest-selenium, pytest-playwright, pytest-bdd&lt;/td&gt;&#xA;          &lt;td&gt;Greffés sur pytest, héritent de son lifecycle&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Vendor SaaS&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;BrowserStack, Sauce Labs, LambdaTest, Cypress Dashboard&lt;/td&gt;&#xA;          &lt;td&gt;Test runner local + cloud d&amp;rsquo;exécution payant&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h3 id=&#34;trois-grandes-surfaces-de-friction-communes&#34;&gt;Trois grandes &lt;em&gt;surfaces de friction&lt;/em&gt; communes&lt;a class=&#34;anchor&#34; href=&#34;#trois-grandes-surfaces-de-friction-communes&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Friction&lt;/th&gt;&#xA;          &lt;th&gt;Manifestation&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Marketing &amp;gt; Technique&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Outils vendus sur démos, conferences et certifications &lt;em&gt;orientées&lt;/em&gt;. La &lt;em&gt;hype&lt;/em&gt; pilote l&amp;rsquo;adoption.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Vendor lock-in&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Une fois adopté, refactor coûteux, &lt;em&gt;prise d&amp;rsquo;otage&lt;/em&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Effort cognitif déplacé&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Comprendre l&amp;rsquo;outil prend plus de temps que de faire son travail.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;2-le-pari-technique-docarina&#34;&gt;2. Le pari technique d&amp;rsquo;Ocarina&lt;a class=&#34;anchor&#34; href=&#34;#2-le-pari-technique-docarina&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Pas de DSL textuel&lt;/strong&gt; → Python pur, &lt;em&gt;embedded DSL&lt;/em&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Pas de plugin pytest&lt;/strong&gt; → runner intégré, fidélité ISTQB absolue.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Pas d&amp;rsquo;&lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt;&lt;/strong&gt; → simplicité, compatibilité Selenium synchrone, pas de « &lt;em&gt;trucs de geeks&lt;/em&gt; ».&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Pas de SaaS&lt;/strong&gt; → tout en local, noyau dur très dense, &lt;em&gt;boîte blanche&lt;/em&gt;.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;Voir aussi : &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/11-independence/03-explicit-refusals/&#34;&gt;&lt;code&gt;../11-independence/03-explicit-refusals.md&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.09 — Ports : ILogger, ITakeScreenshot</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/09-ports/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/09-ports/</guid>
      <description>&lt;h1 id=&#34;0209ports-ilogger-itakescreenshot&#34;&gt;02.09 — Ports : &lt;code&gt;ILogger&lt;/code&gt;, &lt;code&gt;ITakeScreenshot&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0209ports-ilogger-itakescreenshot&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Dossier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/tree/main/src/ocarina/ports&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/ports/&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Deux ports seulement. C&amp;rsquo;est tout. Le reste (&lt;code&gt;WebDriversPool&lt;/code&gt;, &lt;code&gt;Screenshotter&lt;/code&gt;, etc.) vit dans &lt;code&gt;infra/&lt;/code&gt;, pas dans &lt;code&gt;ports/&lt;/code&gt;. La distinction est claire : un &lt;em&gt;port&lt;/em&gt; est une abstraction au-dessus de laquelle vit le DSL ; une &lt;em&gt;infra&lt;/em&gt; est l&amp;rsquo;implémentation des &lt;em&gt;adapters&lt;/em&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;ilogger&#34;&gt;&lt;code&gt;ILogger&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#ilogger&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;ILogger&lt;/span&gt;(ABC):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;set_prefix&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, prefix_thunk: Thunk[&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;]) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; Self: &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;set_domain_taxonomy&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, taxonomy: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;tuple&lt;/span&gt;[&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;]) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; Self: &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;raw&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;args: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;object&lt;/span&gt;, stream: SupportsWrite[&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;] &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;**&lt;/span&gt;kwargs: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;object&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;critical&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, msg: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;args, exc: Exception &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;**&lt;/span&gt;kwargs) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;error&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, msg: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;args, exc: Exception &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;**&lt;/span&gt;kwargs) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;warning&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, msg: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;args, exc: Exception &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;**&lt;/span&gt;kwargs) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;info&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, msg: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;args, exc: Exception &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;**&lt;/span&gt;kwargs) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;debug&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, msg: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;args, exc: Exception &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;**&lt;/span&gt;kwargs) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;test_name&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, msg: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;args, exc: Exception &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;**&lt;/span&gt;kwargs) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;success&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, msg: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;args, exc: Exception &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;**&lt;/span&gt;kwargs) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;exception&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, msg: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;args, exc: Exception &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;**&lt;/span&gt;kwargs) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;cleanup&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Méthode&lt;/th&gt;&#xA;          &lt;th&gt;Sémantique&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;set_prefix(thunk)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Préfixe &lt;strong&gt;paresseux&lt;/strong&gt; appliqué à chaque log (pour timestamps, threads…). Retourne &lt;code&gt;Self&lt;/code&gt; pour chaining.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;set_domain_taxonomy(taxonomy)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Définit la hiérarchie de domaine (&lt;code&gt;(cycle, campaign, suite, test)&lt;/code&gt;). Crucial pour le &lt;code&gt;FileLogger&lt;/code&gt; qui crée une arborescence de fichiers.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;raw(*args, stream=None)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Écriture brute, sans format (utilisé par les plugins de rapport).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;critical / error / warning / info / debug&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Niveaux standards. Chaque méthode accepte &lt;code&gt;exc=&lt;/code&gt; pour passer une exception et la formatter.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;test_name(msg)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Niveau custom : annonce le test en cours (&lt;code&gt;logger.test_name(test.name)&lt;/code&gt;).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;success(msg)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Niveau custom : assertion réussie (utilisé par les &lt;code&gt;.success(...)&lt;/code&gt; handlers).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;exception(msg, exc=)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Logue une exception avec traceback complet.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;cleanup()&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Fin de vie : flush + close (pour le &lt;code&gt;FileLogger&lt;/code&gt;). Recycle le fichier de log entre les retries.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;trois-choses-qui-ne-sont-pas-dans-ilogger&#34;&gt;Trois choses qui ne sont &lt;em&gt;pas&lt;/em&gt; dans &lt;code&gt;ILogger&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#trois-choses-qui-ne-sont-pas-dans-ilogger&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Pas de &lt;code&gt;set_level()&lt;/code&gt;&lt;/strong&gt; : le niveau est géré par l&amp;rsquo;implémentation (ex. &lt;code&gt;MutedLogger&lt;/code&gt; filtre tout).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Pas de &lt;code&gt;add_handler()&lt;/code&gt;&lt;/strong&gt; : pas d&amp;rsquo;API à la &lt;code&gt;logging.Logger&lt;/code&gt; du stdlib. La composition se fait par instanciation de loggers différents (&lt;code&gt;PrintAndFileLogger&lt;/code&gt; wrappe les deux).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Pas de &lt;code&gt;child(name)&lt;/code&gt;&lt;/strong&gt; : la taxonomy est passée &lt;em&gt;en bloc&lt;/em&gt; avec &lt;code&gt;set_domain_taxonomy&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;set_prefixthunk&#34;&gt;&lt;code&gt;set_prefix(thunk)&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#set_prefixthunk&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;logger&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;set_prefix(&lt;span style=&#34;color:#ff79c6&#34;&gt;lambda&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;[&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;datetime&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;now()&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;isoformat()&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;]&amp;#34;&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Si on passait une &lt;code&gt;str&lt;/code&gt;, ce serait calculé &lt;em&gt;au moment du &lt;code&gt;set_prefix&lt;/code&gt;&lt;/em&gt;, donc figé. Avec un &lt;code&gt;Thunk&lt;/code&gt;, le préfixe est calculé &lt;em&gt;à chaque appel de log&lt;/em&gt;. Idéal pour les timestamps.&lt;/p&gt;</description>
    </item>
    <item>
      <title>07.09 — catch_me_if_you_can</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/09-watcher-catch-me/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/09-watcher-catch-me/</guid>
      <description>&lt;h1 id=&#34;0709catch_me_if_you_can&#34;&gt;07.09 — &lt;code&gt;catch_me_if_you_can&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0709catch_me_if_you_can&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Détecte les éléments parasites qui pop sur la page &lt;em&gt;pendant&lt;/em&gt; que le test tourne, et les trace.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;callback&#34;&gt;Callback&lt;a class=&#34;anchor&#34; href=&#34;#callback&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/lib/ext/selenium/watchers/catch_me_if_you_can_watcher.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;catch_me_if_you_can_cb&lt;/span&gt;(watcher: SeleniumWatcher) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    elements &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; watcher&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;driver&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;execute_script(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;return Array.from(document.querySelectorAll(&amp;#39;.catch-me-if-you-can&amp;#39;));&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;not&lt;/span&gt; elements:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    raw &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; watcher&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;driver&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;execute_script(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        return arguments[0].map(el =&amp;gt; ({&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;            tag:       el.tagName.toLowerCase(),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;            text:      el.innerText.trim(),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;            id:        el.id,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;            cls:       el.className,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;            name:      el.getAttribute(&amp;#39;name&amp;#39;) || &amp;#39;&amp;#39;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;            testid:    el.getAttribute(&amp;#39;data-testid&amp;#39;) || &amp;#39;&amp;#39;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        }));&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        elements,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; attrs &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; raw:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        fingerprint &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;:&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;join(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;filter&lt;/span&gt;(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                [&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    attrs[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;tag&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    attrs[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;text&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    attrs[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;id&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    attrs[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;cls&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    attrs[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;name&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    attrs[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;testid&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                ],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; fingerprint &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; watcher&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;cache:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;continue&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        watcher&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;cache&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;add(fingerprint)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        watcher&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;report(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;catch-me-if-you-can element detected: &amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;attrs[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;tag&amp;#39;&lt;/span&gt;]&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;attrs[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;text&amp;#39;&lt;/span&gt;]&lt;span style=&#34;color:#f1fa8c&#34;&gt;!r}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            label&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;CATCH_ME_IF_YOU_CAN&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;mécaniques&#34;&gt;Mécaniques&lt;a class=&#34;anchor&#34; href=&#34;#m%c3%a9caniques&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;1-javascript-pour-bypass-implicit-wait&#34;&gt;1. Javascript pour bypass implicit wait&lt;a class=&#34;anchor&#34; href=&#34;#1-javascript-pour-bypass-implicit-wait&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;elements &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; watcher&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;driver&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;execute_script(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;return Array.from(document.querySelectorAll(&amp;#39;.catch-me-if-you-can&amp;#39;));&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Pas de &lt;code&gt;find_elements(By.CSS_SELECTOR, ...)&lt;/code&gt;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>08.09 — CI : ai_proof_ci.yml &#43; ai_proof_e2e.yml</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/08-ai-example/09-ci-matrix/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/08-ai-example/09-ci-matrix/</guid>
      <description>&lt;h1 id=&#34;0809ci-ai_proof_ciyml--ai_proof_e2eyml&#34;&gt;08.09 — CI : &lt;code&gt;ai_proof_ci.yml&lt;/code&gt; + &lt;code&gt;ai_proof_e2e.yml&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0809ci-ai_proof_ciyml--ai_proof_e2eyml&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Deux workflows. PR rapide (lint+typecheck). e2e manuel avec &lt;strong&gt;matrice Firefox/Chrome&lt;/strong&gt; + warm-up Heroku + filtrage stacktrace ChromeDriver.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;ai_proof_ciyml&#34;&gt;&lt;code&gt;ai_proof_ci.yml&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#ai_proof_ciyml&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: ai_proof CI&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;on&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;push&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;branches&lt;/span&gt;: [main]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;pull_request&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;workflow_dispatch&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;defaults&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;shell&lt;/span&gt;: bash&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;jobs&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;lint-and-typecheck&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;runs-on&lt;/span&gt;: ubuntu-latest&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;steps&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: actions/checkout@v4&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: actions/setup-python@v5&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;python-version&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;3.14&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;allow-prereleases&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;true&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: actions/cache@v4&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;path&lt;/span&gt;: .venv&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;key&lt;/span&gt;: venv-${{ runner.os }}-py${{ steps.setup-python.outputs.python-version }}-${{ hashFiles(&amp;#39;pyproject.toml&amp;#39;) }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Install dependencies&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt;: steps.cache-venv.outputs.cache-hit != &amp;#39;true&amp;#39;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;: |&lt;span style=&#34;color:#f1fa8c&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          python -m venv .venv&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          .venv/bin/pip install . ruff mypy mypy-extensions typing-extensions pre-commit&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Ruff format check&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;: .venv/bin/ruff format src/ --check&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Ruff lint&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;: .venv/bin/ruff check src/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Mypy&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;: .venv/bin/mypy src/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Step&lt;/th&gt;&#xA;          &lt;th&gt;Cmd&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Ruff format check&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ruff format src/ --check&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Ruff lint&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ruff check src/&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Mypy&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;mypy src/&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;ai_proof_e2eyml&#34;&gt;&lt;code&gt;ai_proof_e2e.yml&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#ai_proof_e2eyml&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: ai_proof e2e&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;on&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;workflow_dispatch&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;defaults&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;shell&lt;/span&gt;: bash&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;jobs&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;e2e&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: e2e (${{ matrix.browser }})&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;runs-on&lt;/span&gt;: ubuntu-latest&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;env&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;FORCE_JAVASCRIPT_ACTIONS_TO_NODE24&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;true&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;WAIT_TIMEOUT&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;15&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;strategy&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;fail-fast&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;matrix&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;include&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          - &lt;span style=&#34;color:#ff79c6&#34;&gt;browser&lt;/span&gt;: firefox&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;driver&lt;/span&gt;: geckodriver&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          - &lt;span style=&#34;color:#ff79c6&#34;&gt;browser&lt;/span&gt;: chrome&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;driver&lt;/span&gt;: chromedriver&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;steps&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: actions/checkout@v4&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: actions/setup-python@v5&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;python-version&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;3.14&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;allow-prereleases&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;true&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: actions/cache@v4&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;path&lt;/span&gt;: .venv&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;key&lt;/span&gt;: venv-${{ runner.os }}-py${{ steps.setup-python.outputs.python-version }}-${{ hashFiles(&amp;#39;pyproject.toml&amp;#39;) }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Install dependencies&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt;: steps.cache-venv.outputs.cache-hit != &amp;#39;true&amp;#39;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;: |&lt;span style=&#34;color:#f1fa8c&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          python -m venv .venv&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          .venv/bin/pip install . ruff mypy mypy-extensions typing-extensions pre-commit&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#6272a4&#34;&gt;# --- Firefox ---&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Install geckodriver&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt;: matrix.browser == &amp;#39;firefox&amp;#39;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;: |&lt;span style=&#34;color:#f1fa8c&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          GECKO_VERSION=0.36.0&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          wget -q https://github.com/mozilla/geckodriver/releases/download/v$GECKO_VERSION/geckodriver-v$GECKO_VERSION-linux64.tar.gz&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          tar -xzf geckodriver-v$GECKO_VERSION-linux64.tar.gz&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          chmod +x geckodriver&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Setup Firefox&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt;: matrix.browser == &amp;#39;firefox&amp;#39;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: browser-actions/setup-firefox@fcf821c621167805dd63a29662bd7cb5676c81a8&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#6272a4&#34;&gt;# --- Chrome ---&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Install chromedriver&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt;: matrix.browser == &amp;#39;chrome&amp;#39;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;: |&lt;span style=&#34;color:#f1fa8c&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          CHROME_VER=$(google-chrome-stable --version | grep -oP &amp;#39;\d+\.\d+\.\d+\.\d+&amp;#39;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          wget -q -O chromedriver.zip \&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;            &amp;#34;https://storage.googleapis.com/chrome-for-testing-public/${CHROME_VER}/linux64/chromedriver-linux64.zip&amp;#34;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          unzip -q chromedriver.zip&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          mv chromedriver-linux64/chromedriver ./chromedriver&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          chmod +x ./chromedriver&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#6272a4&#34;&gt;# --- Warm-up ---&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Warm up Heroku dyno&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;: |&lt;span style=&#34;color:#f1fa8c&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          # CURA runs on a Heroku dyno that sleeps after inactivity.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          # Tests hitting a cold dyno produce timeouts (false fails) and slow&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          # confirmations (false passes on gap tests). Wake it explicitly&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          # before any browser opens.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          curl -sf --retry 6 --retry-delay 5 --retry-all-errors \&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;            --max-time 30 https://katalon-demo-cura.herokuapp.com/ &amp;gt; /dev/null&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          echo &amp;#34;Dyno is warm&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#6272a4&#34;&gt;# --- Run ---&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Run e2e cycle&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;: |&lt;span style=&#34;color:#f1fa8c&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          # ChromeDriver release builds ship without debug symbols. On errors it&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          # dumps raw C++ stack frames (&amp;#34;#N 0x&amp;lt;addr&amp;gt; &amp;lt;unknown&amp;gt;&amp;#34;) into the WebDriver&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          # response, which Selenium surfaces as the exception message — polluting&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          # both the live log and the pretty-print summary with unreadable hex noise.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          # No Python-side fix exists. Strip the frames with sed before they land.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          # pipefail preserves Python&amp;#39;s exit code through the pipe.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          set -o pipefail&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          .venv/bin/python -u src/main.py \&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;            --driver-path ./${{ matrix.driver }} \&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;            --browser ${{ matrix.browser }} \&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;            --workers 3 \&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;            --wait-timeout ${{ env.WAIT_TIMEOUT }} \&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;            --logger terminal+file \&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;            2&amp;gt;&amp;amp;1 | sed -u \&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;              -e &amp;#39;/Stacktrace:/{N;/\n#[0-9][0-9]* 0x[0-9a-f][0-9a-f]* &amp;lt;unknown&amp;gt;/{s/Stacktrace:\n[^\n]*/HIDDEN/;b};P;D}&amp;#39; \&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;              -e &amp;#39;/^#[0-9][0-9]* 0x[0-9a-f][0-9a-f]* &amp;lt;unknown&amp;gt;/d&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Enumerate traces&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt;: always()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;: |&lt;span style=&#34;color:#f1fa8c&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          sync&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          find $GITHUB_WORKSPACE/.screenshots -type f 2&amp;gt;/dev/null || true&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          find $GITHUB_WORKSPACE/.ocarina_logs -type f 2&amp;gt;/dev/null || true&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          find $GITHUB_WORKSPACE/.reports -type f 2&amp;gt;/dev/null || true&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Upload screenshots&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt;: always()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: actions/upload-artifact@v4&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: screenshots-${{ matrix.browser }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;path&lt;/span&gt;: .screenshots/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;retention-days&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;7&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#6272a4&#34;&gt;# … upload logs, reports similar ...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;analyse&#34;&gt;Analyse&lt;a class=&#34;anchor&#34; href=&#34;#analyse&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;fail-fast-false&#34;&gt;&lt;code&gt;fail-fast: false&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#fail-fast-false&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;strategy&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;fail-fast&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;matrix&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;include&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;browser&lt;/span&gt;: firefox&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;browser&lt;/span&gt;: chrome&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;fail-fast: false&lt;/code&gt; = si Firefox fail, Chrome continue. &lt;strong&gt;Indispensable&lt;/strong&gt; pour ce projet : la valeur du run est de voir les &lt;strong&gt;deux&lt;/strong&gt; browsers, même si l&amp;rsquo;un fail.&lt;/p&gt;</description>
    </item>
    <item>
      <title>09.03.09 — Skills Setup</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/03-skills/09-setup/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/03-skills/09-setup/</guid>
      <description>&lt;h1 id=&#34;090309skills-setup&#34;&gt;09.03.09 — Skills Setup&lt;a class=&#34;anchor&#34; href=&#34;#090309skills-setup&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Deux skills : &lt;code&gt;setup-environment&lt;/code&gt; (onboarding d&amp;rsquo;un nouveau contributeur, humain ou IA) et &lt;code&gt;profile-environment&lt;/code&gt; (cadre la latitude laissée à l&amp;rsquo;IA pour une mission donnée). Tous deux alimentent le &lt;code&gt;CLAUDE.md&lt;/code&gt; assemblé de la suite.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;le-skill&#34;&gt;Le skill&lt;a class=&#34;anchor&#34; href=&#34;#le-skill&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;input  : un repo frais&#xA;output : étapes d&amp;#39;onboarding :&#xA;            1. venv&#xA;            2. pip install (deps dev + ocarina)&#xA;            3. ruff / mypy / pre-commit installés&#xA;            4. CLAUDE.local.md créé (avec demande paths à l&amp;#39;utilisateur)&#xA;            5. smoke-check du runner (un test minimal qui valide que tout marche)&#xA;            6. boucle pré-commit testée&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;étape-1-venv&#34;&gt;Étape 1 : venv&lt;a class=&#34;anchor&#34; href=&#34;#%c3%a9tape-1-venv&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;python -m venv .venv&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;source&lt;/span&gt; .venv/bin/activate&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;étape-2-pip-install&#34;&gt;Étape 2 : pip install&lt;a class=&#34;anchor&#34; href=&#34;#%c3%a9tape-2-pip-install&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pip install . ruff mypy mypy-extensions typing-extensions pre-commit&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;étape-3-outillage-de-dev&#34;&gt;Étape 3 : outillage de dev&lt;a class=&#34;anchor&#34; href=&#34;#%c3%a9tape-3-outillage-de-dev&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pre-commit install --config .pre-commit-config.yaml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;étape-4-claudelocalmd&#34;&gt;Étape 4 : &lt;code&gt;CLAUDE.local.md&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#%c3%a9tape-4-claudelocalmd&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;CLAUDE.local.md&lt;/code&gt; est &lt;strong&gt;gitignored&lt;/strong&gt; et contient les paths machine-specifiques.&lt;/p&gt;</description>
    </item>
    <item>
      <title>12.09 — Cohérence de l&#39;écosystème</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/09-ecosystem-coherence/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/09-ecosystem-coherence/</guid>
      <description>&lt;h1 id=&#34;1209cohérence-de-lécosystème&#34;&gt;12.09 — Cohérence de l&amp;rsquo;écosystème&lt;a class=&#34;anchor&#34; href=&#34;#1209coh%c3%a9rence-de-l%c3%a9cosyst%c3%a8me&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Six dépôts, plusieurs langages, plusieurs technologies, plusieurs licences, plusieurs plateformes de déploiement. À première vue, hétérogène. À deuxième lecture, &lt;strong&gt;rigoureusement cohérent&lt;/strong&gt; : chaque pièce sert &lt;em&gt;exactement le même&lt;/em&gt; pari.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;1-vue-densemblesix-dépôts-une-éthique&#34;&gt;1. Vue d&amp;rsquo;ensemble : six dépôts, une éthique&lt;a class=&#34;anchor&#34; href=&#34;#1-vue-densemblesix-d%c3%a9p%c3%b4ts-une-%c3%a9thique&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;┌────────────────────────────────────────────────────────────────────────┐&#xA;│         L&amp;#39;ÉCOSYSTÈME COMME UN TOUT COHÉRENT                            │&#xA;└────────────────────────────────────────────────────────────────────────┘&#xA;&#xA;  ┌──────────────┐    Le framework. ROP, types stricts, refus de plugin&#xA;  │   ocarina    │    pytest. UNE seule dep d&amp;#39;exécution. Auditable en&#xA;  └─────────────┬┘    une après-midi. — PROUVE que la rigueur est légère.&#xA;                │&#xA;                │&#xA;                ▼&#xA;  ┌─────────────────┐ La référence d&amp;#39;usage. Adapters projet. Patterns&#xA;  │ ocarina-example │ canoniques. Démontre comment on rétrécit la surface&#xA;  └─────────────┬───┘ d&amp;#39;API du framework pour son contexte. — PROUVE que&#xA;                │     le framework est utilisable « par n&amp;#39;importe qui ».&#xA;                │&#xA;                ▼&#xA;  ┌─────────────────────────┐ Le pari IA. Co-écrit par Claude 99%. CURA&#xA;  │ ocarina-with-ai-example │ Healthcheck comme SUT. Findings sourcés jusqu&amp;#39;au PHP.&#xA;  └─────────────┬───────────┘ — PROUVE que l&amp;#39;IA + Ocarina peut faire un&#xA;                │               vrai travail de testeur senior.&#xA;                ▼&#xA;       ┌──────────────┐ Le SUT volontairement chaotique. Le terrain.&#xA;       │  igoristan   │ — PROUVE qu&amp;#39;on peut tester une appli pathologique&#xA;       └────────┬─────┘   sans tricher.&#xA;                │&#xA;                ▼&#xA;       ┌────────────────┐ Le backend de coordination. Vercel Edge,&#xA;       │ tests-workers  │ Upstash Redis, anti-précision volontaire.&#xA;       └────────┬───────┘ — PROUVE qu&amp;#39;on peut faire de la coordination&#xA;                │           distribuée sans plateforme propriétaire.&#xA;                │&#xA;                ▼&#xA;       ┌───────────────────┐ La documentation publique. VitePress + FR/EN/RU&#xA;       │ ocarina-holy-book │ + LLM-first (llms.txt, CLAUDE.md, skills).&#xA;       └───────────────────┘ — PROUVE qu&amp;#39;on peut écrire de la doc qui sert&#xA;                               humain ET IA, sans plateforme tierce.&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;2-un-dépôtun-type-de-défi&#34;&gt;2. Un dépôt = un type de défi&lt;a class=&#34;anchor&#34; href=&#34;#2-un-d%c3%a9p%c3%b4tun-type-de-d%c3%a9fi&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Dépôt&lt;/th&gt;&#xA;          &lt;th&gt;Type de défi&lt;/th&gt;&#xA;          &lt;th&gt;Pari prouvé&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ocarina&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Construire un framework petit et profond&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;La rigueur n&amp;rsquo;est pas une usine à gaz&lt;/em&gt; »&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ocarina-example&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Démontrer un usage canonique&lt;/td&gt;&#xA;          &lt;td&gt;« Tu peux &lt;em&gt;vraiment&lt;/em&gt; écrire des tests rigoureux comme ça »&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ocarina-with-ai-example&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Faire collaborer un humain et une IA&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;L&amp;rsquo;IA est le pont, pas le DSL&lt;/em&gt; »&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;igoristan&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Bâtir un SUT &lt;em&gt;chaotique&lt;/em&gt;&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;Pas de démo sur un site qui fait vingt lignes d&amp;rsquo;HTML, une démo sur un vrai bordel&lt;/em&gt; »&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;tests-workers&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Être le substrat par lequel les tests se coordonnent&lt;/td&gt;&#xA;          &lt;td&gt;« On peut être &lt;em&gt;stateless&lt;/em&gt; côté code »&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ocarina-holy-book&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Documenter pour humains &lt;em&gt;et&lt;/em&gt; IA&lt;/td&gt;&#xA;          &lt;td&gt;« &lt;em&gt;La documentation est avant tout de la donnée brute pour les LLMs&lt;/em&gt; »&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;Si on enlève l&amp;rsquo;un de ces dépôts, l&amp;rsquo;écosystème &lt;strong&gt;perd un argument&lt;/strong&gt;.&lt;br&gt;&#xA;Les six sont &lt;strong&gt;solidaires&lt;/strong&gt;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>07.10 — EnvGetters</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/10-env-getters/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/10-env-getters/</guid>
      <description>&lt;h1 id=&#34;0710envgetters&#34;&gt;07.10 — &lt;code&gt;EnvGetters&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0710envgetters&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Accesseur &lt;strong&gt;typé&lt;/strong&gt; aux variables d&amp;rsquo;environnement. Évite les typos, refuse les clés inconnues à la compilation, apporte l&amp;rsquo;autocomplétion dans l&amp;rsquo;IDE.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;code&#34;&gt;Code&lt;a class=&#34;anchor&#34; href=&#34;#code&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/lib/ext/ocarina/adapters/agnostic/env_getters.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; typing &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; Literal&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; types &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; MappingProxyType&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; ocarina.opinionated.infra.env &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; EnvGetters, Effects&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; _CredsKeys &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Literal[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;dashboard&amp;#34;&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; _ValuesKeys &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Literal[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;igor_api_key&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;redis_url&amp;#34;&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_load_env&lt;/span&gt;() &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; dotenv &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; load_dotenv&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    load_dotenv()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;_DEFAULT_EFFECTS &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; (_load_env,)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_EnvGetters&lt;/span&gt;(EnvGetters[_CredsKeys, _ValuesKeys]):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;__init__&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, effects: Effects) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; effect &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; effects:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            effect()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;super&lt;/span&gt;()&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;__init__&lt;/span&gt;(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            credentials&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;dashboard&amp;#34;&lt;/span&gt;: MappingProxyType({&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;login&amp;#34;&lt;/span&gt;: os&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;environ[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;DASH_USERNAME&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;password&amp;#34;&lt;/span&gt;: os&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;environ[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;DASH_PASSWORD&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                }),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            values&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;igor_api_key&amp;#34;&lt;/span&gt;: os&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;environ[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;IGOR_API_KEY&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;redis_url&amp;#34;&lt;/span&gt;: os&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;environ[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;REDIS_URL&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;create_env_getters&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, effects: Effects &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; _EnvGetters:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; effects &lt;span style=&#34;color:#ff79c6&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        effects &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; _DEFAULT_EFFECTS&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; _EnvGetters(effects&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;effects)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;_credskeys-et-_valueskeys&#34;&gt;&lt;code&gt;_CredsKeys&lt;/code&gt; et &lt;code&gt;_ValuesKeys&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#_credskeys-et-_valueskeys&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; _CredsKeys &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Literal[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;dashboard&amp;#34;&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; _ValuesKeys &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Literal[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;igor_api_key&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;redis_url&amp;#34;&lt;/span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Credentials&lt;/strong&gt; : paires login/password (dict immutable via &lt;code&gt;MappingProxyType&lt;/code&gt;).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Values&lt;/strong&gt; : valeurs brutes.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;mappingproxytype&#34;&gt;&lt;code&gt;MappingProxyType&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#mappingproxytype&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;dashboard&amp;#34;&lt;/span&gt;: MappingProxyType({&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;login&amp;#34;&lt;/span&gt;: os&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;environ[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;DASH_USERNAME&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;password&amp;#34;&lt;/span&gt;: os&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;environ[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;DASH_PASSWORD&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}),&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;MappingProxyType&lt;/code&gt; est un wrapper read-only autour d&amp;rsquo;un dict :&lt;/p&gt;</description>
    </item>
    <item>
      <title>09.03.10 — Skills Run</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/03-skills/10-run/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/09-holy-book/03-skills/10-run/</guid>
      <description>&lt;h1 id=&#34;090310skills-run&#34;&gt;09.03.10 — Skills Run&lt;a class=&#34;anchor&#34; href=&#34;#090310skills-run&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Faire remonter les choix à trancher &lt;em&gt;avant&lt;/em&gt; un lancement local, composer la commande, et la rendre. Le skill ne lance jamais l&amp;rsquo;exécution lui-même.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;listing-potentiellement-non-exhaustif&#34;&gt;Listing (potentiellement non exhaustif)&lt;a class=&#34;anchor&#34; href=&#34;#listing-potentiellement-non-exhaustif&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Skill&lt;/th&gt;&#xA;          &lt;th&gt;Cible&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;propose-visual-review&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Propose fenêtré (&lt;code&gt;--not-headless&lt;/code&gt;) vs headless (façon CI) avant une exécution, puis compose la commande&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;propose-visual-review&#34;&gt;&lt;code&gt;propose-visual-review&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#propose-visual-review&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;input  : une exécution locale à venir&#xA;output : le choix fenêtré vs headless :&#xA;            - --not-headless : on regarde le navigateur, utile pour déboguer un flux flaky&#xA;            - headless (façon CI) : plus rapide, identique à ce que fait la CI&#xA;         + le compromis et ce qu&amp;#39;il faut surveiller pendant une exécution fenêtrée&#xA;         + la commande composée, rendue à l&amp;#39;utilisateur pour qu&amp;#39;il la lance&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;rôle&#34;&gt;Rôle&lt;a class=&#34;anchor&#34; href=&#34;#r%c3%b4le&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Faire remonter&lt;/strong&gt; le choix d&amp;rsquo;avant-exécution ; expliquer le compromis.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Composer&lt;/strong&gt; la commande.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Ne pas la lancer&lt;/strong&gt; : c&amp;rsquo;est l&amp;rsquo;utilisateur qui exécute.&lt;/li&gt;&#xA;&lt;/ul&gt;</description>
    </item>
    <item>
      <title>12.10 — Infopreneurs, Tugan Bara, IA</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/10-infopreneurs-tugan-bara-ai/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/10-infopreneurs-tugan-bara-ai/</guid>
      <description>&lt;h1 id=&#34;1210infopreneurs-tugan-bara-ia&#34;&gt;12.10 — Infopreneurs, Tugan Bara, IA&lt;a class=&#34;anchor&#34; href=&#34;#1210infopreneurs-tugan-bara-ia&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;L&amp;rsquo;écosystème &lt;em&gt;opposé&lt;/em&gt; à Ocarina. Pas un autre framework de test : un autre &lt;em&gt;modèle culturel&lt;/em&gt;. Il faut nommer ce que combat le Holy Book pour comprendre &lt;em&gt;pourquoi&lt;/em&gt; il combat. Et pourquoi l&amp;rsquo;IA est en train de tuer ce modèle.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;1-quest-ce-que-linfopreneuriat&#34;&gt;1. Qu&amp;rsquo;est-ce que l&amp;rsquo;« &lt;em&gt;infopreneuriat&lt;/em&gt; »&lt;a class=&#34;anchor&#34; href=&#34;#1-quest-ce-que-linfopreneuriat&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;définition&#34;&gt;Définition&lt;a class=&#34;anchor&#34; href=&#34;#d%c3%a9finition&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;&lt;strong&gt;Infopreneur&lt;/strong&gt; = entrepreneur dont le produit est de &lt;em&gt;l&amp;rsquo;information vendue&lt;/em&gt; : formations en ligne, programmes de coaching, e-books, masterminds, abonnements à une « &lt;em&gt;newsletter premium&lt;/em&gt; ». Le produit n&amp;rsquo;est pas physique.&lt;/p&gt;</description>
    </item>
    <item>
      <title>07.11 — CI</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/11-ci/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/11-ci/</guid>
      <description>&lt;h1 id=&#34;0711ci&#34;&gt;07.11 — CI&lt;a class=&#34;anchor&#34; href=&#34;#0711ci&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Trois workflows : &lt;code&gt;main_ci.yml&lt;/code&gt; (rapide, lint + typecheck), &lt;code&gt;dev_ci.yml&lt;/code&gt; (idem pour branches dev/feature/fix), &lt;code&gt;e2e.yml&lt;/code&gt; (manuel, service Redis + Firefox).&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;vue-densemble&#34;&gt;Vue d&amp;rsquo;ensemble&lt;a class=&#34;anchor&#34; href=&#34;#vue-densemble&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Workflow&lt;/th&gt;&#xA;          &lt;th&gt;Trigger&lt;/th&gt;&#xA;          &lt;th&gt;Plateforme&lt;/th&gt;&#xA;          &lt;th&gt;Effet&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;main_ci.yml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;push/PR main&lt;/td&gt;&#xA;          &lt;td&gt;ubuntu + windows × py 3.14&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;make check-coding-style&lt;/code&gt; (mypy + ruff). &lt;em&gt;Pas de browser.&lt;/em&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;dev_ci.yml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;push dev / feature/_ / fix/_&lt;/td&gt;&#xA;          &lt;td&gt;ubuntu × py 3.14&lt;/td&gt;&#xA;          &lt;td&gt;id., mais Ubuntu seul&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;e2e.yml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;workflow_dispatch&lt;/code&gt; (manuel)&lt;/td&gt;&#xA;          &lt;td&gt;ubuntu + service Redis + Firefox + geckodriver&lt;/td&gt;&#xA;          &lt;td&gt;Run complet de la suite e2e. Upload &lt;code&gt;.screenshots/&lt;/code&gt;, &lt;code&gt;.ocarina_logs/&lt;/code&gt;, &lt;code&gt;.reports/&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;main_ciyml&#34;&gt;&lt;code&gt;main_ci.yml&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#main_ciyml&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Main branch CI&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;on&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;push&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;branches&lt;/span&gt;: [main]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;pull_request&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;branches&lt;/span&gt;: [main]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;workflow_dispatch&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;defaults&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;shell&lt;/span&gt;: bash&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;jobs&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;test&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;runs-on&lt;/span&gt;: ${{ matrix.os }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;strategy&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;matrix&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;os&lt;/span&gt;: [ubuntu-latest, windows-latest]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;python-version&lt;/span&gt;: [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;3.14&amp;#34;&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;steps&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: actions/checkout@v6&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: actions/setup-python@v6&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt;: { &lt;span style=&#34;color:#ff79c6&#34;&gt;python-version&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;3.14&amp;#34;&lt;/span&gt; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: actions/cache@v5&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;path&lt;/span&gt;: .venv&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;key&lt;/span&gt;: venv-${{ runner.os }}-py3.14-${{ hashFiles(&amp;#39;pyproject.toml&amp;#39;) }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Install&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;: make install-on-ci&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Check coding style&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;: |&lt;span style=&#34;color:#f1fa8c&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          . .venv/bin/activate&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          make check-coding-style&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Matrice OS&lt;/strong&gt; : ubuntu + windows. Vérifie la portabilité Python du code projet.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Pas de browser&lt;/strong&gt; : on lance juste &lt;code&gt;make check-coding-style&lt;/code&gt; (= &lt;code&gt;mypy + ruff&lt;/code&gt;). Rapide, pas besoin de Selenium / Redis / OTP.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Cache &lt;code&gt;.venv&lt;/code&gt;&lt;/strong&gt; : reuse entre runs.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;dev_ciyml&#34;&gt;&lt;code&gt;dev_ci.yml&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#dev_ciyml&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;on&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;push&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;branches&lt;/span&gt;: [dev, &amp;#34;feature/**&amp;#34;, &amp;#34;fix/**&amp;#34;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;workflow_dispatch&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;jobs&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;test&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;runs-on&lt;/span&gt;: ubuntu-latest&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#6272a4&#34;&gt;# … identique à main_ci.yml, juste Ubuntu seul …&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Identique au &lt;code&gt;main_ci.yml&lt;/code&gt; mais Ubuntu seul (Windows ne run que sur main).&lt;/p&gt;</description>
    </item>
    <item>
      <title>12.11 — L&#39;industrie SaaS, la fraude à l&#39;ARR, jeu de dupes</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/11-saas-arr-fraud/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/11-saas-arr-fraud/</guid>
      <description>&lt;h1 id=&#34;1211lindustrie-saas-la-fraude-à-larr-jeu-de-dupes&#34;&gt;12.11 — L&amp;rsquo;industrie SaaS, la fraude à l&amp;rsquo;ARR, jeu de dupes&lt;a class=&#34;anchor&#34; href=&#34;#1211lindustrie-saas-la-fraude-%c3%a0-larr-jeu-de-dupes&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Ocarina &lt;strong&gt;rejette le SaaS&lt;/strong&gt;. L&amp;rsquo;industrie du SaaS est un &lt;strong&gt;écosystème circulaire&lt;/strong&gt; où chaque outil vend à chaque autre outil, où le revenu déclaré est gonflé par convention, et où chaque étage de la chaîne capitalistique (LP, VC, founder) entretient sciemment une &lt;strong&gt;plausible deniability&lt;/strong&gt; pour pouvoir attaquer les autres en justice le moment venu.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;1-le-saas-contemporain&#34;&gt;1. Le SaaS contemporain&lt;a class=&#34;anchor&#34; href=&#34;#1-le-saas-contemporain&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Couche&lt;/th&gt;&#xA;          &lt;th&gt;Fournisseurs (échantillon)&lt;/th&gt;&#xA;          &lt;th&gt;Modèle&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Hosting &amp;amp; CDN&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Vercel, Netlify, Cloudflare, Fly.io, Render&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Abonnement par projet / par bandwidth&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;DB&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Supabase, Neon, PlanetScale, Upstash, MongoDB Atlas&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Abonnement par compute / par GB&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Auth&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Clerk, Auth0, WorkOS, Stytch&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Abonnement par MAU&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Mail&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Resend, SendGrid, Postmark, Mailgun&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Abonnement par mail envoyé&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Analytics&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;PostHog, Mixpanel, Amplitude, Plausible, Vercel Analytics&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Abonnement par event&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Error tracking&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Sentry, BugSnag, Rollbar, Highlight&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Abonnement par event&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Observability&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Datadog, New Relic, Honeycomb, Grafana Cloud, Axiom&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Abonnement par metric volume&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Feature flags&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;LaunchDarkly, GrowthBook, Statsig, ConfigCat&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Abonnement par MAU&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Test e2e&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;BrowserStack, Sauce Labs, LambdaTest, Cypress Dashboard&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Abonnement par run / par parallel&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;CI&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;GitHub Actions, CircleCI, Buildkite, Vercel CI&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Abonnement par minute&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Code review&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Graphite, Codeball, CodeRabbit, Sourcery&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Abonnement par PR / par dev&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Comms internes&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Slack, Linear, Notion, Figma, Loom, Pitch&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Abonnement par seat&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Billing&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Stripe, Lago, Orb, Metronome&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;% du revenu traité&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;CRM&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;HubSpot, Salesforce, Attio, Pipedrive&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Abonnement par seat&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Support&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Intercom, Zendesk, Plain, Help Scout&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Abonnement par seat + par message&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;AI tooling&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;OpenAI, Anthropic, Cursor, Vercel AI SDK, Replicate&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Abonnement par token + par seat&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;2-abonnements-croisés&#34;&gt;2. Abonnements croisés&lt;a class=&#34;anchor&#34; href=&#34;#2-abonnements-crois%c3%a9s&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;   ┌──────────┐ paie     ┌──────────┐&#xA;   │ Vercel   │─────────&amp;gt;│ Stripe   │&#xA;   │          │&amp;lt;─────────│          │&#xA;   └──────────┘     paie └──────────┘&#xA;        │                     │&#xA;   paie │                     │ paie&#xA;        v                     v&#xA;   ┌──────────┐          ┌──────────┐&#xA;   │ Linear   │&amp;lt;────────&amp;gt;│ Notion   │&#xA;   └──────────┘  paient  └──────────┘&#xA;        │                     │&#xA;        v                     v&#xA;   ┌───────────────────────────────┐&#xA;   │ Slack, Figma, Loom, Sentry,   │&#xA;   │ Datadog, Clerk, Cursor, …     │&#xA;   │ tous abonnés à tous           │&#xA;   └───────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Vercel&lt;/strong&gt; paie un abonnement à &lt;em&gt;Stripe&lt;/em&gt; (billing), &lt;em&gt;Datadog&lt;/em&gt; (obs), &lt;em&gt;Linear&lt;/em&gt; (planning), &lt;em&gt;Figma&lt;/em&gt; (design), &lt;em&gt;Loom&lt;/em&gt; (présentations internes), &lt;em&gt;Slack&lt;/em&gt; (comms), &lt;em&gt;Notion&lt;/em&gt; (docs).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Stripe&lt;/strong&gt; paie un abonnement à &lt;em&gt;Vercel&lt;/em&gt; (hosting pour ses dashboards), &lt;em&gt;Linear&lt;/em&gt;, &lt;em&gt;Figma&lt;/em&gt;, &lt;em&gt;Slack&lt;/em&gt;…&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Linear&lt;/strong&gt; paie un abonnement à &lt;em&gt;Vercel&lt;/em&gt;, &lt;em&gt;Stripe&lt;/em&gt;, &lt;em&gt;Notion&lt;/em&gt;, &lt;em&gt;Figma&lt;/em&gt;, &lt;em&gt;Slack&lt;/em&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Notion&lt;/strong&gt; paie un abonnement à &lt;em&gt;Linear&lt;/em&gt;, &lt;em&gt;Stripe&lt;/em&gt;, &lt;em&gt;Slack&lt;/em&gt;, &lt;em&gt;Figma&lt;/em&gt;.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Et ainsi de suite. &lt;strong&gt;Tout le monde paie tout le monde.&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.12 — Custom types &amp; custom errors</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/12-custom-types-errors/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/12-custom-types-errors/</guid>
      <description>&lt;h1 id=&#34;0212custom-types--custom-errors&#34;&gt;02.12 — Custom types &amp;amp; custom errors&lt;a class=&#34;anchor&#34; href=&#34;#0212custom-types--custom-errors&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Couche &lt;em&gt;shape&lt;/em&gt; d&amp;rsquo;Ocarina. Aucune logique : juste des types, des alias, des exceptions, des protocols. C&amp;rsquo;est ce qui rend le DSL typé sans logique runtime additionnelle.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;custom_types&#34;&gt;&lt;code&gt;custom_types/&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#custom_types&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Fichier&lt;/th&gt;&#xA;          &lt;th&gt;Contenu&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;effect.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;type Effect = Callable[[], None]&lt;/code&gt; + &lt;code&gt;type Effects = tuple[Effect, ...]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;thunk.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;type Thunk[T] = Callable[[], T]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;tpom.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;TypeVar TPOM bound POMBase&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;supports_write.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Protocol SupportsWrite[T]&lt;/code&gt; (&lt;code&gt;write(s: T) -&amp;gt; Any&lt;/code&gt;)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;built_web_driver.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;type BuiltWebDriver[Driver] = tuple[Driver, Effect]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;scenario.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Scenario[Driver]&lt;/code&gt; (frozen dataclass)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;test_components.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestChain&lt;/code&gt;, &lt;code&gt;TestSetup&lt;/code&gt;, &lt;code&gt;TestTeardown&lt;/code&gt;, &lt;code&gt;TestWatchers[Driver]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;test_runner.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestRunner[Driver]&lt;/code&gt; (frozen dataclass)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;oc_test.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestName&lt;/code&gt;, &lt;code&gt;TestScenario[Driver]&lt;/code&gt;, &lt;code&gt;TestScenarioFragment[Driver]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;oc_test_layers.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestId&lt;/code&gt;, &lt;code&gt;TestResult&lt;/code&gt;, &lt;code&gt;TestSuiteResult&lt;/code&gt;, &lt;code&gt;TestSuiteResults&lt;/code&gt;, &lt;code&gt;TestCampaignResults&lt;/code&gt;, &lt;code&gt;TestCycleResults&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;selenium/built_web_driver.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;BuiltSeleniumWebDriver = BuiltWebDriver[WebDriver]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;selenium/oc_test_scenario.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;SeleniumTestScenario = TestScenario[WebDriver]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;selenium/supported_browsers.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;type SupportedSeleniumBrowser = Literal[&amp;quot;chrome&amp;quot;, &amp;quot;firefox&amp;quot;, &amp;quot;edge&amp;quot;, &amp;quot;safari&amp;quot;]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;selenium/web_drivers_pool.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;type SeleniumWebDriversPool = WebDriversPool[WebDriver]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;Tous ces fichiers sont &lt;em&gt;excluded de la couverture&lt;/em&gt; (&lt;code&gt;pyproject.toml#tool.coverage&lt;/code&gt;) :&lt;/p&gt;</description>
    </item>
    <item>
      <title>12.12 — DHH, Paul Graham, Haters, Mean People Fail</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/12-dhh-paul-graham-haters-mean/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/12-dhh-paul-graham-haters-mean/</guid>
      <description>&lt;h1 id=&#34;1212dhh-paul-graham-haters-mean-people-fail&#34;&gt;12.12 — DHH, Paul Graham, &lt;em&gt;Haters&lt;/em&gt;, &lt;em&gt;Mean People Fail&lt;/em&gt;&lt;a class=&#34;anchor&#34; href=&#34;#1212dhh-paul-graham-haters-mean-people-fail&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Le Holy Book cite deux grands noms : &lt;strong&gt;David Heinemeier Hansson (DHH)&lt;/strong&gt; et &lt;strong&gt;Paul Graham (pg)&lt;/strong&gt;. Ces deux auteurs ne sont pas cités au hasard, chacun incarne une &lt;strong&gt;posture rhétorique&lt;/strong&gt; précise. Ce fichier explique qui ils sont, ce qu&amp;rsquo;ils ont écrit, et pourquoi l&amp;rsquo;essai &lt;em&gt;Haters&lt;/em&gt; de Paul Graham (et son frère cadet &lt;em&gt;Mean People Fail&lt;/em&gt;) sont le &lt;em&gt;précédent intellectuel&lt;/em&gt; direct du chapitre « &lt;em&gt;Premiers retours&lt;/em&gt; ».&lt;/p&gt;</description>
    </item>
    <item>
      <title>12.13 — Lambda-calcul, ROP, Haskell / F# / OCaml, monades</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/13-lambda-calculus-rop-origins/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/13-lambda-calculus-rop-origins/</guid>
      <description>&lt;h1 id=&#34;1213lambda-calcul-rop-haskell-f-ocaml-monades&#34;&gt;12.13 — Lambda-calcul, ROP, Haskell / F# / OCaml, monades&lt;a class=&#34;anchor&#34; href=&#34;#1213lambda-calcul-rop-haskell-f-ocaml-monades&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Ocarina n&amp;rsquo;invente rien sur le plan théorique. Il &lt;strong&gt;applique&lt;/strong&gt; : du &lt;strong&gt;lambda-calcul&lt;/strong&gt; d&amp;rsquo;Alonzo Church (1936) aux &lt;strong&gt;monades&lt;/strong&gt; d&amp;rsquo;Eugenio Moggi (1989) et Philip Wadler (1992-95), formalisé en tant que &lt;strong&gt;Railway Oriented Programming&lt;/strong&gt; par Scott Wlaschin (2014).&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;1-lambda-calcul-church-1936&#34;&gt;1. Lambda-calcul (Church, 1936)&lt;a class=&#34;anchor&#34; href=&#34;#1-lambda-calcul-church-1936&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;définition&#34;&gt;Définition&lt;a class=&#34;anchor&#34; href=&#34;#d%c3%a9finition&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;Le &lt;strong&gt;lambda-calcul&lt;/strong&gt; (λ-calcul) est un système formel inventé par &lt;strong&gt;Alonzo Church&lt;/strong&gt; (1903-1995) pour étudier la &lt;strong&gt;calculabilité&lt;/strong&gt;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>12.14 — L&#39;IA, et comment Ocarina l&#39;applique pour de vrai</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/14-real-ai-in-ocarina/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/14-real-ai-in-ocarina/</guid>
      <description>&lt;h1 id=&#34;1214lia-et-comment-ocarina-lapplique-pour-de-vrai&#34;&gt;12.14 — L&amp;rsquo;IA, et comment Ocarina l&amp;rsquo;applique &lt;em&gt;pour de vrai&lt;/em&gt;&lt;a class=&#34;anchor&#34; href=&#34;#1214lia-et-comment-ocarina-lapplique-pour-de-vrai&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;« &lt;em&gt;AI-powered&lt;/em&gt; » est devenu en 2023-2026 un slogan marketing complètement débile : 99 % des produits qui le revendiquent enrichissent un produit existant avec un endpoint &lt;code&gt;chat.completions&lt;/code&gt;. Ocarina propose, à l&amp;rsquo;opposé, une &lt;strong&gt;infrastructure logicielle pensée pour qu&amp;rsquo;une IA puisse contribuer comme un développeur senior&lt;/strong&gt;. &lt;a href=&#34;https://github.com/mojo-molotov/ocarina-with-ai-example&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;ocarina-with-ai-example&lt;/code&gt;&lt;/a&gt; étant la preuve par l&amp;rsquo;exemple.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;1-ce-quest-lia-aujourdhui&#34;&gt;1. Ce qu&amp;rsquo;est l&amp;rsquo;IA aujourd&amp;rsquo;hui&lt;a class=&#34;anchor&#34; href=&#34;#1-ce-quest-lia-aujourdhui&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Couche&lt;/th&gt;&#xA;          &lt;th&gt;Exemple&lt;/th&gt;&#xA;          &lt;th&gt;Date d&amp;rsquo;apparition&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Statistical learning&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Régression, k-means, decision trees&lt;/td&gt;&#xA;          &lt;td&gt;1960s-1990s&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Deep learning&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;CNN (vision), RNN/LSTM (texte)&lt;/td&gt;&#xA;          &lt;td&gt;2012 (AlexNet — adoption mainstream)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Transformer-based foundation models&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;GPT, Claude, Gemini, Llama&lt;/td&gt;&#xA;          &lt;td&gt;2017 (papier), 2018 (BERT), 2022 (ChatGPT)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Workflows agentiques&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;AutoGPT, LangChain, Claude Code, Cursor agents&lt;/td&gt;&#xA;          &lt;td&gt;2023+&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;&lt;em&gt;Note : le CNN est inventé par Yann LeCun dès 1989 (MNIST). AlexNet (Krizhevsky, Sutskever, Hinton, Toronto, 2012) en est la percée industrielle. LeCun, Hinton et Bengio partagent le Turing Award 2018 pour leurs contributions fondatrices au deep learning.&lt;/em&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>12.15 — Typage, ISTQB, Reinforcement Learning, probes : pourquoi l&#39;IA marche dans Ocarina</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/15-typing-ai-rl-probes/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/15-typing-ai-rl-probes/</guid>
      <description>&lt;h1 id=&#34;1215typage-istqb-reinforcement-learning-probes-pourquoi-lia-marche-dans-ocarina&#34;&gt;12.15 — Typage, ISTQB, Reinforcement Learning, &lt;em&gt;probes&lt;/em&gt; : pourquoi l&amp;rsquo;IA marche dans Ocarina&lt;a class=&#34;anchor&#34; href=&#34;#1215typage-istqb-reinforcement-learning-probes-pourquoi-lia-marche-dans-ocarina&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Pourquoi tant d&amp;rsquo;&lt;strong&gt;obsession&lt;/strong&gt; pour le typage strict, la formalisation de chaque interface, l&amp;rsquo;alignement sur l&amp;rsquo;&lt;strong&gt;ISTQB&lt;/strong&gt;, le refus d&amp;rsquo;exécuter une assertion sans avoir &lt;em&gt;observé&lt;/em&gt; ? Le mécanisme est emprunté au &lt;strong&gt;Reinforcement Learning&lt;/strong&gt; : un agent ne progresse que sous &lt;strong&gt;contrainte&lt;/strong&gt; + &lt;strong&gt;signal de récompense&lt;/strong&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;1-un-llm-nu-nest-quun-mauvais-collaborateur&#34;&gt;1. Un LLM nu n&amp;rsquo;est qu&amp;rsquo;un mauvais collaborateur&lt;a class=&#34;anchor&#34; href=&#34;#1-un-llm-nu-nest-quun-mauvais-collaborateur&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Un LLM sans environnement structuré est :&lt;/p&gt;</description>
    </item>
    <item>
      <title>12.16 — L&#39;internet souterrain des années 1995-2010 : forums, outils, banalisation de la cruauté</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/16-underground-internet-2000s/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/16-underground-internet-2000s/</guid>
      <description>&lt;h1 id=&#34;1216linternet-souterrain-des-années-1995-2010-forums-outils-banalisation-de-la-cruauté&#34;&gt;12.16 — L&amp;rsquo;internet souterrain des années 1995-2010 : forums, outils, banalisation de la cruauté&lt;a class=&#34;anchor&#34; href=&#34;#1216linternet-souterrain-des-ann%c3%a9es-1995-2010-forums-outils-banalisation-de-la-cruaut%c3%a9&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Pour comprendre la rage qui traverse le Holy Book d&amp;rsquo;Ocarina, il faut comprendre &lt;strong&gt;ce qu&amp;rsquo;était &lt;em&gt;vraiment&lt;/em&gt; internet&lt;/strong&gt; avant Facebook (2006-2008), avant que Cloudflare ne devienne le &lt;em&gt;gardien des portes&lt;/em&gt; (2010+), avant que la &lt;em&gt;modération&lt;/em&gt; ne soit prise au sérieux. C&amp;rsquo;était un &lt;em&gt;Far West&lt;/em&gt; où des compétences étatiques se développaient dans des chambres d&amp;rsquo;adolescents, où la &lt;em&gt;vie privée&lt;/em&gt; d&amp;rsquo;un adversaire pouvait être démolie en 48 heures, et où les recours juridiques classiques étaient &lt;strong&gt;inopérants&lt;/strong&gt;. Cette page décrit la mécanique, sans complaisance, sans héroïsation.&lt;/p&gt;</description>
    </item>
    <item>
      <title>12.17 — La psyché du survivant : « ennemis », « narcissisme », « enfer », « survie », et comment ça remonte jusque dans le code</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/17-survivor-psyche-programming/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/17-survivor-psyche-programming/</guid>
      <description>&lt;h1 id=&#34;1217la-psyché-du-survivantennemis-narcissisme-enfer-survie-et-comment-ça-remonte-jusque-dans-le-code&#34;&gt;12.17 — La psyché du survivant : « &lt;em&gt;ennemis&lt;/em&gt; », « &lt;em&gt;narcissisme&lt;/em&gt; », « &lt;em&gt;enfer&lt;/em&gt; », « &lt;em&gt;survie&lt;/em&gt; », et comment ça remonte jusque dans le code&lt;a class=&#34;anchor&#34; href=&#34;#1217la-psych%c3%a9-du-survivantennemis-narcissisme-enfer-survie-et-comment-%c3%a7a-remonte-jusque-dans-le-code&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;L&amp;rsquo;auteur du Holy Book d&amp;rsquo;Ocarina utilise des mots forts : &lt;strong&gt;ennemis&lt;/strong&gt;, &lt;strong&gt;narcissisme&lt;/strong&gt;, &lt;strong&gt;enfer sur terre&lt;/strong&gt;, &lt;strong&gt;survie&lt;/strong&gt;. Ces mots ne sont pas des effets de style. Ce sont les &lt;strong&gt;descripteurs précis&lt;/strong&gt; d&amp;rsquo;une expérience vécue par toute une cohorte qui a traversé l&amp;rsquo;internet souterrain 2000s décrit dans &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/16-underground-internet-2000s/&#34;&gt;&lt;code&gt;16-underground-internet-2000s.md&lt;/code&gt;&lt;/a&gt;. Cette page explique pourquoi ces mots sont &lt;em&gt;exacts&lt;/em&gt;, ce que produit ce vécu sur la psyché, et comment ces adaptations psychologiques s&amp;rsquo;inscrivent &lt;em&gt;profondément&lt;/em&gt; dans la façon de programmer, d&amp;rsquo;architecturer, de &lt;em&gt;toujours se tenir à jour&lt;/em&gt;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>12.18 — « Vilains but not Monsters », DEF CON, « live as a white hat or die as a black hat », et l&#39;idéologie d&#39;Ocarina</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/18-villains-defcon-ocarina-ideology/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/18-villains-defcon-ocarina-ideology/</guid>
      <description>&lt;h1 id=&#34;1218vilains-but-not-monsters-def-con-live-as-a-white-hat-or-die-as-a-black-hat-et-lidéologie-docarina&#34;&gt;12.18 — « &lt;em&gt;Vilains but not Monsters&lt;/em&gt; », DEF CON, « &lt;em&gt;live as a white hat or die as a black hat&lt;/em&gt; », et l&amp;rsquo;idéologie d&amp;rsquo;Ocarina&lt;a class=&#34;anchor&#34; href=&#34;#1218vilains-but-not-monsters-def-con-live-as-a-white-hat-or-die-as-a-black-hat-et-lid%c3%a9ologie-docarina&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Ocarina n&amp;rsquo;est ni une lubie esthétique ni un OVNI hors-sol. C&amp;rsquo;est l&amp;rsquo;expression technique d&amp;rsquo;une &lt;strong&gt;idéologie cohérente&lt;/strong&gt; héritée du mouvement hacker : l&amp;rsquo;éthique &lt;strong&gt;Vilain, mais pas un monstre&lt;/strong&gt;, la &lt;strong&gt;culture tribale&lt;/strong&gt; des conférences (DEF CON en tête), et la &lt;strong&gt;devise&lt;/strong&gt; de YTCracker prononcée sur scène : « &lt;em&gt;live as a white hat, or die as a black hat&lt;/em&gt; ».&lt;/p&gt;</description>
    </item>
    <item>
      <title>12.19 — It&#39;s over</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/19-its-over/</link>
      <pubDate>Sun, 31 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/12-manifesto-deciphered/19-its-over/</guid>
      <description>&lt;h1 id=&#34;1219its-over&#34;&gt;12.19 — It&amp;rsquo;s over&lt;a class=&#34;anchor&#34; href=&#34;#1219its-over&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;p&gt;Je n&amp;rsquo;écris pas du code « qui implémente des specs ».&lt;br&gt;&#xA;J&amp;rsquo;écris du code avec &lt;strong&gt;une âme&lt;/strong&gt;.&lt;br&gt;&#xA;Du vrai code de passionné. Tu sais ce que ça veut dire ? Pendant que toi, tu prends ça de haut, que tu nous traites d&amp;rsquo;autistes ?&lt;/p&gt;&#xA;&lt;p&gt;On ne demande rien, si ce n&amp;rsquo;est du respect et qu&amp;rsquo;on nous laisse travailler normalement.&lt;br&gt;&#xA;La situation actuelle de l&amp;rsquo;informatique est épuisante.&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
