<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: opaopa6969</title>
    <description>The latest articles on DEV Community by opaopa6969 (@opaopa6969).</description>
    <link>https://dev.to/opaopa6969</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3852460%2F96148190-0b90-4e07-816d-0f4313b57d89.jpeg</url>
      <title>DEV Community: opaopa6969</title>
      <link>https://dev.to/opaopa6969</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/opaopa6969"/>
    <language>en</language>
    <item>
      <title>I built a state machine where invalid transitions can't compile</title>
      <dc:creator>opaopa6969</dc:creator>
      <pubDate>Thu, 09 Apr 2026 05:54:57 +0000</pubDate>
      <link>https://dev.to/opaopa6969/i-built-a-state-machine-where-invalid-transitions-cant-compile-14fk</link>
      <guid>https://dev.to/opaopa6969/i-built-a-state-machine-where-invalid-transitions-cant-compile-14fk</guid>
      <description>&lt;p&gt;You know this bug.&lt;/p&gt;

&lt;p&gt;You're building an OAuth flow. Ten states. Five callbacks. A token refresh that fires at exactly the wrong moment. And then, three hours into debugging:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Wait — when we reach &lt;code&gt;CALLBACK_RECEIVED&lt;/code&gt;, does &lt;code&gt;authorizationCode&lt;/code&gt; definitely exist?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You grep. You trace. You add a null check. You deploy. Two weeks later, a different state, a different missing field, the same three hours.&lt;/p&gt;

&lt;p&gt;I got tired of this. So I built &lt;a href="https://github.com/opaopa6969/tramli" rel="noopener noreferrer"&gt;tramli&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What tramli does in 30 seconds
&lt;/h2&gt;

&lt;p&gt;tramli is a constrained flow engine. You declare states, transitions, and — this is the key part — &lt;strong&gt;what data each processor needs and what it produces&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;OrderState&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;FlowState&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="no"&gt;CREATED&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
    &lt;span class="no"&gt;PAYMENT_PENDING&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
    &lt;span class="no"&gt;CONFIRMED&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
    &lt;span class="no"&gt;SHIPPED&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Tramli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;define&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OrderState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initiallyAvailable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;CREATED&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;auto&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;PAYMENT_PENDING&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StartPayment&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;PAYMENT_PENDING&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;CONFIRMED&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PaymentGuard&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;CONFIRMED&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;auto&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SHIPPED&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ShipOrder&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// ← THIS is where the magic happens&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;build()&lt;/code&gt; runs &lt;strong&gt;8 validation checks&lt;/strong&gt; at build time:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Exactly one initial state&lt;/li&gt;
&lt;li&gt;At least one terminal state&lt;/li&gt;
&lt;li&gt;Every non-terminal state has outgoing transitions&lt;/li&gt;
&lt;li&gt;Every state is reachable from initial&lt;/li&gt;
&lt;li&gt;At least one terminal is reachable (unless perpetual)&lt;/li&gt;
&lt;li&gt;No duplicate transitions from the same state&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;requires/produces chain is satisfied on every path&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;No orphan states&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Check #7 is what kills the OAuth bug. If &lt;code&gt;ShipOrder.requires()&lt;/code&gt; declares &lt;code&gt;PaymentResult.class&lt;/code&gt;, but no processor on any path to &lt;code&gt;CONFIRMED&lt;/code&gt; produces it — &lt;strong&gt;&lt;code&gt;build()&lt;/code&gt; throws at startup&lt;/strong&gt;. Not at 2 AM. Not in production. At startup.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Just use XState"
&lt;/h2&gt;

&lt;p&gt;Fair question. XState is great — 29K GitHub stars, proper Statecharts with hierarchy and parallel states. I respect it.&lt;/p&gt;

&lt;p&gt;But XState doesn't have &lt;code&gt;requires/produces&lt;/code&gt;. You can freely read and write &lt;code&gt;context&lt;/code&gt; from any state. The type system helps, but it can't prove "this field is always present when we reach this state across every possible path."&lt;/p&gt;

&lt;p&gt;tramli trades expressiveness for that guarantee:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;XState:  hierarchy + parallel  → can't verify data-flow (exponential paths)
tramli:  flat enum only        → every path is enumerable → full verification
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a deliberate tradeoff. If you need deep hierarchy and parallel regions, use XState. If you need &lt;strong&gt;proof that your data is always where you expect it&lt;/strong&gt;, use tramli.&lt;/p&gt;

&lt;h2&gt;
  
  
  The data-flow graph
&lt;/h2&gt;

&lt;p&gt;tramli doesn't just validate — it shows you the data flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dataFlowGraph&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toMermaid&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates a Mermaid diagram showing exactly which processor produces which type, and which processor consumes it. Your flow definition IS the documentation. They can never drift apart.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;graph LR
    StartPayment --&amp;gt;|produces PaymentIntent| PaymentGuard
    PaymentGuard --&amp;gt;|produces PaymentResult| ShipOrder
    ShipOrder --&amp;gt;|produces ShipmentInfo| SHIPPED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Real example: OIDC auth flow
&lt;/h2&gt;

&lt;p&gt;This is the flow that started it all. 9 states, 5 processors, and it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;oidc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Tramli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;define&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"oidc"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;AuthState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initiallyAvailable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OidcConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;IDLE&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;auto&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;REDIRECTING&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BuildAuthUrl&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;REDIRECTING&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;CALLBACK_RECEIVED&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CallbackGuard&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;CALLBACK_RECEIVED&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;auto&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;EXCHANGING&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ExchangeCode&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;EXCHANGING&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;auto&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;VALIDATING&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ValidateTokens&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;VALIDATING&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;branch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TokenValidator&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;AUTHENTICATED&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"valid"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;REFRESH_NEEDED&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"expired"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;endBranch&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;REFRESH_NEEDED&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;auto&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;EXCHANGING&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RefreshToken&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;AUTHENTICATED&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SESSION_EXPIRED&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ExpiryGuard&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SESSION_EXPIRED&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;auto&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;IDLE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Cleanup&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onAnyError&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;AUTH_ERROR&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;50 lines. Every data dependency verified. The &lt;code&gt;build()&lt;/code&gt; call proves that &lt;code&gt;ExchangeCode&lt;/code&gt; will always have the &lt;code&gt;authorizationCode&lt;/code&gt; that &lt;code&gt;CallbackGuard&lt;/code&gt; produces. Every. Single. Time.&lt;/p&gt;

&lt;p&gt;A procedural version of this was 1,800 lines and had the token bug I mentioned. This version has zero state-related bugs because they're structurally impossible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not just Java
&lt;/h2&gt;

&lt;p&gt;tramli has implementations in three languages with a shared test suite:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Java&lt;/strong&gt; — reference implementation, 3,000 lines&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript&lt;/strong&gt; — 2,200 lines, same API shape&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rust&lt;/strong&gt; — 2,200 lines, same guarantees&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same validation rules, same &lt;code&gt;requires/produces&lt;/code&gt; contracts, same Mermaid output. A shared test suite in YAML ensures all three behave identically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plugin system
&lt;/h2&gt;

&lt;p&gt;tramli core is deliberately frozen — it's a verification kernel. Everything else is a plugin:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plugin&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;eventstore&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Append-only transition log + replay + compensation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;audit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Produced-data diff capture per transition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hierarchy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Harel-style hierarchical authoring → flat enum code generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lint&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Policy-based design checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;observability&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Telemetry sink integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;idempotency&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Command-ID duplicate suppression&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;diagram&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Mermaid + data-flow bundle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Markdown catalog generation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The hierarchy plugin is interesting — it lets you author in Harel Statechart style, then &lt;strong&gt;compiles down to flat enums&lt;/strong&gt; so data-flow verification still works. Best of both worlds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who is this for?
&lt;/h2&gt;

&lt;p&gt;Honestly? Not most people.&lt;/p&gt;

&lt;p&gt;If your state management is "button clicked → modal opens → modal closes", use Zustand. It's 3KB and it's great.&lt;/p&gt;

&lt;p&gt;tramli is for the people who:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Built an OAuth/OIDC flow and lost hours to "which fields exist in which state?"&lt;/li&gt;
&lt;li&gt;Wrote a payment processing pipeline and found out in production that a race condition skipped a validation step&lt;/li&gt;
&lt;li&gt;Maintained a 2,000-line workflow handler and couldn't tell what would break if they changed line 847&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've debugged a state transition bug at 2 AM and thought "why can't the compiler just catch this?", that's exactly what tramli does.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://github.com/opaopa6969/tramli" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://github.com/opaopa6969/tramli/blob/main/docs/api-cookbook.md" rel="noopener noreferrer"&gt;API Cookbook&lt;/a&gt; · &lt;a href="https://github.com/opaopa6969/tramli/blob/main/docs/why-tramli-works.md" rel="noopener noreferrer"&gt;Why tramli Works&lt;/a&gt; · &lt;a href="https://github.com/opaopa6969/tramli/blob/main/docs/example-oidc-auth-flow.md" rel="noopener noreferrer"&gt;OIDC Example&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Zero dependencies. MIT license. Built by someone who lost too many hours debugging state transitions.&lt;/p&gt;

</description>
      <category>statemachine</category>
      <category>java</category>
      <category>typescript</category>
      <category>rust</category>
    </item>
    <item>
      <title>AI Characters Arguing About Your Code. Yes, Really</title>
      <dc:creator>opaopa6969</dc:creator>
      <pubDate>Tue, 31 Mar 2026 00:10:21 +0000</pubDate>
      <link>https://dev.to/opaopa6969/i-made-my-ai-reviewer-argue-with-itself-kfb</link>
      <guid>https://dev.to/opaopa6969/i-made-my-ai-reviewer-argue-with-itself-kfb</guid>
      <description>&lt;h1&gt;
  
  
  AI Characters Arguing About Your Code. Yes, Really.
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Plain AI reviews what you wrote. DGE finds what you forgot to write.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;npm install @unlaxer/dge-toolkit &amp;amp;&amp;amp; npx dge-install --lang en&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Tell Claude Code "run DGE on this"&lt;/li&gt;
&lt;li&gt;Characters argue about your design — and surface gaps plain AI misses&lt;/li&gt;
&lt;li&gt;3 gaps in 2 minutes. The kind that change your architecture.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Demo (2 min)
&lt;/h3&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/0sG0fMbA_Jo"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With AI Code Review
&lt;/h2&gt;

&lt;p&gt;AI review is great at checking what you wrote — missing validation, style violations, known best practices. It's a smart checklist.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;the hardest bugs live in what you didn't write.&lt;/strong&gt; The unstated assumption. The two features that are fine alone but deadly together. The "why JWT?" that nobody asked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DGE (Dialogue-driven Gap Extraction)&lt;/strong&gt; creates a cast of characters who &lt;em&gt;argue&lt;/em&gt; about your design. A quality guardian, a lazy genius, an attacker, a philosopher — each with a different blind spot they refuse to ignore.&lt;/p&gt;

&lt;p&gt;In between their arguments, gaps emerge.&lt;/p&gt;




&lt;h2&gt;
  
  
  30-Second Setup
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @unlaxer/dge-toolkit
npx dge-install &lt;span class="nt"&gt;--lang&lt;/span&gt; en
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in Claude Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Human: run DGE on the auth API design
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Live Demo: Auth API Review
&lt;/h2&gt;

&lt;p&gt;Here's what actually happened when I DGE'd an auth API:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👤 Columbo&lt;/strong&gt;: "Just one more thing... this refresh token lasts 30 days. Who decided that? Did anyone ask users if they want to stay logged in for a month?"&lt;/p&gt;

&lt;p&gt;→ Gap: Refresh token expiration has no documented rationale&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🎩 Picard&lt;/strong&gt;: "The login response format is undefined. Do you return tokens in the body? Set-Cookie? And on error — if you tell them 'wrong email' vs 'wrong password', you've leaked whether the account exists."&lt;/p&gt;

&lt;p&gt;→ Gap: Login response format undefined + error information leakage&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🎭 Socrates&lt;/strong&gt;: "Everyone is assuming JWT. I detect fallacy #5: 'JWT is the modern standard' — says who? If you have one server, sessions work fine. What's the actual reason for JWT?"&lt;/p&gt;

&lt;p&gt;→ Gap: No technical rationale for JWT over sessions&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3 gaps in 2 minutes.&lt;/strong&gt; These aren't style nits — they change the architecture. The last one — questioning JWT itself — is something plain AI never does. Plain AI accepts your choices and tells you what's missing. DGE questions whether those choices were right.&lt;/p&gt;




&lt;h2&gt;
  
  
  DGE vs Plain AI: Same Spec, Honest Comparison
&lt;/h2&gt;

&lt;p&gt;I ran both on the same auth API spec. The plain AI ran in an &lt;strong&gt;isolated subprocess&lt;/strong&gt; — zero knowledge of DGE's results.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;DGE&lt;/th&gt;
&lt;th&gt;Plain AI (isolated)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Total gaps&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;9&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;28&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Critical&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DGE-only findings&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Plain AI found 3x more gaps. But look at the content:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plain AI&lt;/strong&gt;: "Missing CSRF protection", "No HTTPS enforcement", "No password reset flow" — a &lt;strong&gt;best practices checklist&lt;/strong&gt;. Valid, but you'd find them in any auth guide.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DGE-only&lt;/strong&gt;: "Why JWT?", "Token expiration doesn't match app type", "Schema breaks when you add OAuth" — these question &lt;strong&gt;design decisions themselves&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;They're complementary. DGE toolkit v2 runs both in parallel and merges results automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Isolation Discovery
&lt;/h3&gt;

&lt;p&gt;First time I ran "plain AI" in the same conversation as DGE, it found 15 gaps. In an isolated subprocess: &lt;strong&gt;28 gaps&lt;/strong&gt; — 87% more. The AI was unconsciously avoiding DGE's findings. Isolation matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real World: DGE Found an Attack Chain Nobody Designed
&lt;/h2&gt;

&lt;p&gt;This one's from an actual project. I ran DGE with 5 characters on a terminal multiplexer tool. &lt;strong&gt;10 Critical gaps in one session.&lt;/strong&gt; Here are two that no single-point review would catch:&lt;/p&gt;

&lt;h3&gt;
  
  
  Path Traversal → Data Exfiltration
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;⚔ Levi&lt;/strong&gt;: "Show me the template duplicate code."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;USER_TEMPLATES_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sourceName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"No sanitization on &lt;code&gt;sourceName&lt;/code&gt;. Send &lt;code&gt;../../.ssh/id_ed25519&lt;/code&gt; and your SSH private key gets copied to the templates directory. Then &lt;code&gt;GET /api/templates&lt;/code&gt; reads it out."&lt;/p&gt;

&lt;p&gt;→ Gap: CWE-22 Path Traversal — arbitrary file read via template API&lt;/p&gt;

&lt;h3&gt;
  
  
  Two Safe Features → One Dangerous Combo
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;🏥 House&lt;/strong&gt;: "The &lt;code&gt;/mnt/c/var&lt;/code&gt; mount is read-write. The auto-accept feature sends &lt;code&gt;y&lt;/code&gt; to confirmation prompts. Now imagine a process asks &lt;code&gt;Delete all files? (y/n)&lt;/code&gt; — auto-accept sends &lt;code&gt;y&lt;/code&gt;. Two features, both fine alone, deadly together."&lt;/p&gt;

&lt;p&gt;→ Gap: RW mount + auto-accept = unintended file destruction&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;These aren't single-point bugs.&lt;/strong&gt; They're &lt;em&gt;combinations&lt;/em&gt; that emerge because characters build on each other's findings. A checklist review says "add input validation" and "add authentication" separately. DGE characters chain them into attack paths.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Inside
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;19 characters&lt;/strong&gt; (Columbo, Picard, Holmes, Red Team, Socrates...) — each with distinct blind spots&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3 modes&lt;/strong&gt;: Quick (instant), Design Review (structured), Brainstorm (ideas)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom characters&lt;/strong&gt;: &lt;code&gt;add Batman&lt;/code&gt; — personality analyzed, saved, available next session&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;20 conversation patterns&lt;/strong&gt; × &lt;strong&gt;8 dialogue techniques&lt;/strong&gt; — not random arguing, structured exploration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Details: &lt;a href="https://github.com/opaopa6969/DGE-toolkit" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://github.com/opaopa6969/DGE-toolkit/blob/main/kit/INTERNALS.en.md" rel="noopener noreferrer"&gt;INTERNALS.md&lt;/a&gt; · &lt;a href="https://github.com/opaopa6969/DGE-toolkit/blob/main/kit/CUSTOMIZING.en.md" rel="noopener noreferrer"&gt;CUSTOMIZING.md&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;DGE catches what you forgot to write. Plain AI catches what you wrote wrong. You need both.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;npm: &lt;code&gt;@unlaxer/dge-toolkit&lt;/code&gt; · MIT License&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devtools</category>
      <category>opensource</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
