<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Railway on Ocarina — manual</title>
    <link>https://mojo-molotov.github.io/from-ocarina-to-igor/series/railway/</link>
    <description>Recent content in Railway on Ocarina — manual</description>
    <generator>Hugo</generator>
    <language>en-US</language>
    <lastBuildDate>Thu, 28 May 2026 21:02:53 +0200</lastBuildDate>
    <atom:link href="https://mojo-molotov.github.io/from-ocarina-to-igor/series/railway/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>02.03.01 — The Result[T] type</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/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/02-ocarina/03-railway/01-result/</guid>
      <description>&lt;h1 id=&#34;020301the-resultt-type&#34;&gt;02.03.01 — The &lt;code&gt;Result[T]&lt;/code&gt; type&lt;a class=&#34;anchor&#34; href=&#34;#020301the-resultt-type&#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;Source file: &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;zero dependencies&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-design-decisions-dissected&#34;&gt;Six design decisions, dissected&lt;a class=&#34;anchor&#34; href=&#34;#six-design-decisions-dissected&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;1-_baseresult-as-a-common-parent&#34;&gt;1. &lt;code&gt;_BaseResult&lt;/code&gt; as a common parent&lt;a class=&#34;anchor&#34; href=&#34;#1-_baseresult-as-a-common-parent&#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;One job: let &lt;em&gt;mypy&lt;/em&gt; &lt;strong&gt;read &lt;code&gt;result.error&lt;/code&gt; without narrowing first&lt;/strong&gt;. Without the base, you&amp;rsquo;d have to write:&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.03.02 — The builder state machine</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/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/02-ocarina/03-railway/02-action-chain-states/</guid>
      <description>&lt;h1 id=&#34;020302the-builder-state-machine&#34;&gt;02.03.02 — The builder state machine&lt;a class=&#34;anchor&#34; href=&#34;#020302the-builder-state-machine&#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;Source file: &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;Here&amp;rsquo;s where the &lt;em&gt;entire&lt;/em&gt; ROP mechanic lives: four successive typed states that literally (as in: the &lt;em&gt;type-checker&lt;/em&gt; literally) refuse any syntactic improvisation. The DSL doubles as its own immune system.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;overview&#34;&gt;Overview&lt;a class=&#34;anchor&#34; href=&#34;#overview&#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] │     an 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()   ── runs action(), fires the handler&#xA;                               ▼&#xA;                      ┌─────────────────┐&#xA;                      │ ActionChain[T]  │     holds (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 starts over&#xA;   │ Start[T]         │         │  (the next action │&#xA;   │ (failure rail:   │         │   will execute)   │&#xA;   │  the whole chain │         └───────────────────┘&#xA;   │  becomes no-op,  │&#xA;   │  while keeping   │&#xA;   │  the fluent API) │&#xA;   └──────────────────┘&#xA;              │&#xA;              ▼ (.failure → .success → .execute, all no-op)&#xA;   ┌──────────────────┐&#xA;   │  ActionChain[T]  │   has_failed=True, result=&amp;lt;accumulated Fail&amp;gt;&#xA;   └──────────────────┘&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;the-types-action-failurehandler-succhandler&#34;&gt;The 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;#the-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; is a &lt;code&gt;Thunk&lt;/code&gt;.&lt;/strong&gt; Calling &lt;code&gt;ActionStart(action)&lt;/code&gt; does &lt;strong&gt;not&lt;/strong&gt; run anything. The action just gets captured.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;FailureHandler&lt;/code&gt; gets the exception&lt;/strong&gt;, not the &lt;code&gt;Fail&lt;/code&gt; or the &lt;code&gt;Result&lt;/code&gt;. Deliberate: 99% of handlers want to log the exception, take a screenshot, and move on.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;SuccHandler&lt;/code&gt; is an &lt;code&gt;Effect&lt;/code&gt;&lt;/strong&gt; (no argument). Same idea — a success handler logs a static message, snaps a screenshot, doesn&amp;rsquo;t need the result.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;typestate-pattern-why-you-cant-get-it-wrong&#34;&gt;Typestate Pattern: why you can&amp;rsquo;t get it wrong&lt;a class=&#34;anchor&#34; href=&#34;#typestate-pattern-why-you-cant-get-it-wrong&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;The chain is &lt;strong&gt;strictly linear&lt;/strong&gt;. Each method returns a different type that only exposes the &lt;em&gt;next legal step&lt;/em&gt;:&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.03.03 — The failure rail: NeutralAction*</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/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/02-ocarina/03-railway/03-neutral-rail/</guid>
      <description>&lt;h1 id=&#34;020303the-failure-rail-neutralaction&#34;&gt;02.03.03 — The failure rail: &lt;code&gt;NeutralAction*&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#020303the-failure-rail-neutralaction&#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;Edge case in the builder: what happens when you &lt;em&gt;chain&lt;/em&gt; an action onto an &lt;code&gt;ActionChain&lt;/code&gt; that has already failed?&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;why-neutral-classes-rather-than-a-user-side-if&#34;&gt;Why &lt;em&gt;Neutral&lt;/em&gt; classes rather than a user-side &lt;code&gt;if&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#why-neutral-classes-rather-than-a-user-side-if&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;When you chain an action onto an already-failed &lt;code&gt;ActionChain&lt;/code&gt;, you slide into three &lt;em&gt;Neutral&lt;/em&gt; classes that honor the same API but do nothing.&lt;/p&gt;&#xA;&lt;p&gt;Picture the DSL without this. User code would look like:&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;chain &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; act1&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;failure(h)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;success(h)&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;if&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;    chain &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; chain&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;then(act2)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;failure(h)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;success(h)&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;if&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;    chain &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; chain&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;then(act3)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;failure(h)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;success(h)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;execute()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;Imperative&lt;/em&gt;, unreadable, and the whole point of the DSL is gone. The neutral rail keeps the writing flat:&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.03.04 — chain_actions and ChainRunner</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/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/02-ocarina/03-railway/04-chain-actions-fold/</guid>
      <description>&lt;h1 id=&#34;020304chain_actions-and-chainrunner&#34;&gt;02.03.04 — &lt;code&gt;chain_actions&lt;/code&gt; and &lt;code&gt;ChainRunner&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#020304chain_actions-and-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;Source file: &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;The primitive that makes the DSL &amp;ldquo;flat.&amp;rdquo; Without it, an N-step scenario is a staircase of &lt;code&gt;.then(...).failure(...).success(...).execute()&lt;/code&gt;. With it, it&amp;rsquo;s a list.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;parenthesis-hell&#34;&gt;“Parenthesis hell”&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;(Excerpt from the docstring.)&lt;/em&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.03.05 — create_act and its hooks</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/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/02-ocarina/03-railway/05-create-act-hooks/</guid>
      <description>&lt;h1 id=&#34;020305create_act-and-its-hooks&#34;&gt;02.03.05 — &lt;code&gt;create_act&lt;/code&gt; and its hooks&lt;a class=&#34;anchor&#34; href=&#34;#020305create_act-and-its-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;Source file: &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;The &lt;strong&gt;low-level primitive&lt;/strong&gt; that builds a &lt;em&gt;test step&lt;/em&gt; constructor. You call it &lt;strong&gt;once&lt;/strong&gt; to mint a project-specific &lt;code&gt;act&lt;/code&gt; verb. In theory a complex project might want several distinct &lt;code&gt;act&lt;/code&gt; verbs with different hooks, but no real-world need has shown up yet — recommendation: create &lt;strong&gt;one&lt;/strong&gt;. Not a singleton, just a convention.&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;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;create_act&lt;/span&gt;(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    pom: TPOM,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    action: Callable[[TPOM], 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;*&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    on_failure: Callable[[TPOM, Exception], Fail] &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;    on_run_effect: Effect &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;    act_counter_effect: Effect &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; ActionStart[TPOM]:&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;Parameter&lt;/th&gt;&#xA;          &lt;th&gt;Position&lt;/th&gt;&#xA;          &lt;th&gt;Type&lt;/th&gt;&#xA;          &lt;th&gt;Role&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;pom&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;positional&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;TPOM&lt;/code&gt; (bound &lt;code&gt;POMBase&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;The page (or any &lt;code&gt;POMBase&lt;/code&gt; subclass) you&amp;rsquo;re acting on.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;action&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;positional&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Callable[[TPOM], TPOM]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;The function that acts on the page and returns the page (fluent). Identity-typed.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;on_failure&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;keyword-only&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Callable[[TPOM, Exception], Fail] | None&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Hook for &lt;strong&gt;refining&lt;/strong&gt; the &lt;code&gt;Fail&lt;/code&gt;: if you reach this, you&amp;rsquo;ve already failed; the hook just lets you type the error more precisely (e.g. &lt;code&gt;HttpErrorPageReachedError&lt;/code&gt;).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;on_run_effect&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;keyword-only&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Effect | None&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Side effect called &lt;strong&gt;before&lt;/strong&gt; the action (e.g.: log a step number).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;act_counter_effect&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;keyword-only&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Effect | None&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Additional&lt;/em&gt; side effect called before the action. Default: increments the act counter.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&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;run_action&lt;/span&gt;() &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; Result[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;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;if&lt;/span&gt; act_counter_effect:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act_counter_effect()&#xA;&lt;/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;            ThreadsBasedActCounter()&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;incr_act_call_count()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display: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; on_run_effect:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            on_run_effect()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display: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_pom &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; action(pom)&#xA;&lt;/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; Ok(result_pom)&#xA;&lt;/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:  &lt;span style=&#34;color:#6272a4&#34;&gt;# noqa: BLE001&lt;/span&gt;&#xA;&lt;/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; on_failure:&#xA;&lt;/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; on_failure(pom, 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;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; ActionStart(run_action)&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;The &lt;code&gt;try&lt;/code&gt; wraps EVERYTHING&lt;/strong&gt;: &lt;code&gt;act_counter_effect&lt;/code&gt;, &lt;code&gt;on_run_effect&lt;/code&gt;, &lt;em&gt;and&lt;/em&gt; the action. If any side effect raises (say &lt;code&gt;on_run_effect&lt;/code&gt; hits a dead resource), it counts as a test-step failure. Everything that runs &amp;ldquo;during the test step&amp;rdquo; IS the test step.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Order is deterministic&lt;/strong&gt;: counter → run effect → action. The counter ticks &lt;em&gt;even if&lt;/em&gt; the action ends up failing. Intentional — we want to know how many steps were &lt;em&gt;attempted&lt;/em&gt;, not how many passed.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;on_failure&lt;/code&gt; can return an enriched &lt;code&gt;Fail&lt;/code&gt;&lt;/strong&gt;: that&amp;rsquo;s where a &lt;code&gt;WebDriverException&lt;/code&gt; becomes an &lt;code&gt;HttpErrorPageReachedError&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;default-actcounter-threadsbasedactcounter&#34;&gt;Default &lt;code&gt;ActCounter&lt;/code&gt;: &lt;code&gt;ThreadsBasedActCounter&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#default-actcounter-threadsbasedactcounter&#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; ocarina.opinionated.infra.act_counter &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; ActCounter &lt;span style=&#34;color:#ff79c6&#34;&gt;as&lt;/span&gt; ThreadsBasedActCounter&#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:#ff79c6&#34;&gt;if&lt;/span&gt; act_counter_effect:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    act_counter_effect()&#xA;&lt;/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;    ThreadsBasedActCounter()&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;incr_act_call_count()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Source: &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;src/ocarina/opinionated/infra/act_counter.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.03.06 — drive_page</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/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/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;Source file: &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;&lt;strong&gt;Literally&lt;/strong&gt; &lt;code&gt;chain_actions&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-this-alias&#34;&gt;Why this alias?&lt;a class=&#34;anchor&#34; href=&#34;#why-this-alias&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;1-semantics-im-taking-control-of-a-page&#34;&gt;1. Semantics: &amp;ldquo;I&amp;rsquo;m taking control of a page&amp;rdquo;&lt;a class=&#34;anchor&#34; href=&#34;#1-semantics-im-taking-control-of-a-page&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;Holy Book quote (&amp;ldquo;First scenarios&amp;rdquo;):&lt;/p&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;&lt;code&gt;drive_page&lt;/code&gt; expresses the fact that you are taking control of &lt;em&gt;one&lt;/em&gt; page.&#xA;Any &lt;em&gt;transition&lt;/em&gt; becomes explicit through the opening of a new &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;# ⬅ closing control of the 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;# ⬅ opening control of the next page&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;Mixing acts from two different POMs inside one &lt;code&gt;drive_page&lt;/code&gt; is a &lt;em&gt;mypy&lt;/em&gt; error (see &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/02-ocarina/03-railway/02-action-chain-states/&#34;&gt;&lt;code&gt;02-action-chain-states.md&lt;/code&gt;&lt;/a&gt;, &amp;ldquo;narrowing via &lt;code&gt;act()&lt;/code&gt;&amp;rdquo; section). The semantics get enforced concretely.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.03.07 — match_page / when</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/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/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;Source file: &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;Bolted onto the framework &lt;strong&gt;after the fact&lt;/strong&gt;. The Holy Book puts it bluntly: &amp;ldquo;&lt;em&gt;&lt;code&gt;match_page&lt;/code&gt; and &lt;code&gt;when&lt;/code&gt; were added after the fact; the Igoristan was so random that the use case forced itself onto us. Their implementation was simple, proof of the grammar&amp;rsquo;s flexibility: other analogous structures could follow just as easily.&lt;/em&gt;&amp;rdquo;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;the-problem&#34;&gt;The problem&lt;a class=&#34;anchor&#34; href=&#34;#the-problem&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Some pages render differently:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Cookies banner showing or not.&lt;/li&gt;&#xA;&lt;li&gt;A/B test serving two UI variants.&lt;/li&gt;&#xA;&lt;li&gt;Degraded mode (maintenance page) instead of the real page.&lt;/li&gt;&#xA;&lt;li&gt;Anti-bot captcha.&lt;/li&gt;&#xA;&lt;li&gt;…&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;The scenario needs to &lt;strong&gt;branch&lt;/strong&gt; without breaking the ROP chain.&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
