<?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: Arnold Holm</title>
    <description>The latest articles on DEV Community by Arnold Holm (@stratcorealpha).</description>
    <link>https://dev.to/stratcorealpha</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4078355%2Fc2ecba14-1ac4-43ef-b437-2a38e810c2ca.png</url>
      <title>DEV Community: Arnold Holm</title>
      <link>https://dev.to/stratcorealpha</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/stratcorealpha"/>
    <language>en</language>
    <item>
      <title>A Missing Risk Check Is Not a "No"</title>
      <dc:creator>Arnold Holm</dc:creator>
      <pubDate>Wed, 23 Sep 2026 07:26:50 +0000</pubDate>
      <link>https://dev.to/stratcorealpha/a-missing-risk-check-is-not-a-no-5gik</link>
      <guid>https://dev.to/stratcorealpha/a-missing-risk-check-is-not-a-no-5gik</guid>
      <description>&lt;p&gt;A decision trace says &lt;code&gt;WAIT&lt;/code&gt;. Did the risk check actually return false, or did its service time out?&lt;/p&gt;

&lt;p&gt;Both paths may submit no order, but they need different fixes. An observed rejection means inspect the rule and its inputs. A timeout means restore the source of evidence. If the trace calls both &lt;code&gt;WAIT&lt;/code&gt;, the second problem can sit there unnoticed.&lt;/p&gt;

&lt;p&gt;My &lt;a href="https://dev.to/stratcorealpha/a-decision-trace-must-distinguish-false-from-missing-2nci"&gt;earlier example&lt;/a&gt; kept &lt;code&gt;false&lt;/code&gt; separate from an absent field. &lt;a href="https://dev.to/raju_dandigam/comment/3f6j4"&gt;Raju's follow-up question&lt;/a&gt; points to the next boundary: an observation can be present in the record but unavailable or invalid. The decision itself must respect that status.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give each input an evidence state
&lt;/h2&gt;

&lt;p&gt;This small example has two required checks: &lt;code&gt;routeConnected&lt;/code&gt; and &lt;code&gt;riskAllowed&lt;/code&gt;. It is a decision-trace example, not an order executor.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Recorded input&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Decision state&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;observed&lt;/code&gt;, &lt;code&gt;value: true&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The check ran and passed&lt;/td&gt;
&lt;td&gt;Continue to the other checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;observed&lt;/code&gt;, &lt;code&gt;value: false&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The check ran and failed&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;WAIT&lt;/code&gt;, if all required observations are valid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;unavailable&lt;/code&gt;, with reason and attempt ID&lt;/td&gt;
&lt;td&gt;The source did not provide a result&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BLOCKED&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;invalid&lt;/code&gt;, with reason and source ID&lt;/td&gt;
&lt;td&gt;A result arrived but failed validation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BLOCKED&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No input record&lt;/td&gt;
&lt;td&gt;The trace is incomplete&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BLOCKED&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The important order is: inspect &lt;strong&gt;all required inputs&lt;/strong&gt; for missing or unusable evidence, then evaluate the observed booleans. Otherwise an early &lt;code&gt;false&lt;/code&gt; can hide a later timeout.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small decision boundary
&lt;/h2&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;gateNames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;routeConnected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;riskAllowed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;decide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;trace&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decisionId&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decisionId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BLOCKED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;missing_decision_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inspected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gate&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;gateNames&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;observations&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasOwn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;observations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BLOCKED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;missing_observation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;observation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;observations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;observation&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;observation&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BLOCKED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invalid_envelope&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;observation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;observed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;observation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;boolean&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;observation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sourceEventId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BLOCKED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invalid_observed_value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;inspected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;observation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;evidenceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;observation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sourceEventId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;observation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unavailable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;observation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invalid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;observation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;observation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;evidenceId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BLOCKED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;missing_failure_evidence&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BLOCKED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;observation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;observation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;evidenceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;observation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;evidenceId&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BLOCKED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unknown_status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;failed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inspected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;observation&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;observation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WAIT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;observed_false&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;evidenceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;evidenceId&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;READY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;all_observed_true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, &lt;code&gt;riskAllowed: { status: 'unavailable', reason: 'risk_service_timeout', evidenceId: 'poll-21' }&lt;/code&gt; produces &lt;code&gt;BLOCKED&lt;/code&gt;. The &lt;code&gt;poll-21&lt;/code&gt; reference names the failed attempt. It does not pretend there was a risk observation. By contrast, &lt;code&gt;{ status: 'observed', value: false, sourceEventId: 'risk-20' }&lt;/code&gt; produces &lt;code&gt;WAIT&lt;/code&gt; and points to the actual result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tests that make the distinction visible
&lt;/h2&gt;

&lt;p&gt;I ran this boundary with Node's built-in test runner. All six local cases passed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Two observed &lt;code&gt;true&lt;/code&gt; inputs produce &lt;code&gt;READY&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;An observed &lt;code&gt;false&lt;/code&gt; produces &lt;code&gt;WAIT&lt;/code&gt; with its source event ID.&lt;/li&gt;
&lt;li&gt;An unavailable risk service produces &lt;code&gt;BLOCKED&lt;/code&gt;, never &lt;code&gt;WAIT&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;An invalid risk value produces &lt;code&gt;BLOCKED&lt;/code&gt; with a reason.&lt;/li&gt;
&lt;li&gt;A missing required observation produces &lt;code&gt;BLOCKED&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A false route check cannot hide an unavailable risk check. The result remains &lt;code&gt;BLOCKED&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The sixth case matters most. A system can decline to place an order in both cases while still telling its operator the wrong reason. The trace should preserve whether it saw a negative result or lacked evidence to decide.&lt;/p&gt;

&lt;p&gt;This example only tests the decision mapping. It does not prove that a source event ID exists, that an observation is fresh, or that a broker accepted or filled anything. A production trace should retain observation time and source identity, reject stale records, and join the decision to any later order request and fill as separate events.&lt;/p&gt;

&lt;p&gt;To check your own boundary, remove one required input, replace another with &lt;code&gt;false&lt;/code&gt;, then make its source unavailable. If all three cases show the same state and reason, the trace still loses a failure mode.&lt;/p&gt;

&lt;p&gt;Reference: &lt;a href="https://nodejs.org/api/test.html" rel="noopener noreferrer"&gt;Node.js test runner&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>javascript</category>
      <category>automation</category>
      <category>fintech</category>
    </item>
    <item>
      <title>A Successful Response Can Still Belong to the Wrong Screen</title>
      <dc:creator>Arnold Holm</dc:creator>
      <pubDate>Sun, 20 Sep 2026 18:23:33 +0000</pubDate>
      <link>https://dev.to/stratcorealpha/a-successful-response-can-still-belong-to-the-wrong-screen-34a</link>
      <guid>https://dev.to/stratcorealpha/a-successful-response-can-still-belong-to-the-wrong-screen-34a</guid>
      <description>&lt;p&gt;A trading dashboard requests an instrument preview. Before the response arrives, the user selects another instrument. Both requests succeed. The older one finishes last and replaces the newer view.&lt;/p&gt;

&lt;p&gt;Nothing failed at the transport layer. The bug is accepting a result that no longer belongs to the current selection.&lt;/p&gt;

&lt;p&gt;This article uses a small synthetic JavaScript fixture. It is a UI acceptance example, not a broker integration or an order-routing system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give each selection its own identity
&lt;/h2&gt;

&lt;p&gt;Comparing only the instrument name is insufficient. Consider ES, then NQ, then ES again. A response from the first ES request must not become valid merely because the screen says ES again.&lt;/p&gt;

&lt;p&gt;For one in-memory JavaScript session, a fresh object can serve as the request ticket:&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;function&lt;/span&gt; &lt;span class="nf"&gt;session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;view&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;instrument&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="nx"&gt;view&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ticket&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;view&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ticket is created when the selection changes, captured by the request, and checked immediately before committing the result. The server does not need to know this local object.&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;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;session&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;first&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ES&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NQ&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;latest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ES&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stale&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fresh&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No timer or live service is required to exercise the failure. Deliver responses in the wrong order on purpose. That makes the acceptance rule reproducible instead of hoping a slow network reveals it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four checks, including the same-symbol case
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Sequence&lt;/th&gt;
&lt;th&gt;Expected result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Select ES, complete its current request&lt;/td&gt;
&lt;td&gt;ES result appears&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Select ES then NQ, complete NQ then ES&lt;/td&gt;
&lt;td&gt;NQ remains visible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Select ES, NQ, ES; complete the first ES request&lt;/td&gt;
&lt;td&gt;Old result is rejected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Display ES, then select NQ before completion&lt;/td&gt;
&lt;td&gt;Old result is cleared&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I ran these four cases with Node's built-in test runner. All four passed. They test this small in-memory acceptance function only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cancellation and acceptance do different jobs
&lt;/h2&gt;

&lt;p&gt;Cancelling obsolete work can save resources. A separate acceptance check answers whether a completion may change the current view. Apply the same ownership check to success, error and loading-state updates; an old error should not replace a newer successful result either.&lt;/p&gt;

&lt;p&gt;Object identity is deliberately local. A process boundary, persisted request or reload needs an explicit request identifier and a defined lifetime. This fixture also does not validate the payload, prove data freshness, handle authorization or prevent duplicate broker actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to put in a repair brief
&lt;/h2&gt;

&lt;p&gt;“The wrong symbol sometimes appears” is difficult to reproduce. Specify the selection sequence, completion order and exact view expected after each completion. Include the switch-away-and-back case. That one distinguishes selection identity from a coincidentally matching symbol name.&lt;/p&gt;

&lt;p&gt;The useful boundary is simple: a successful response is allowed to update the screen only while its request still owns that screen state.&lt;/p&gt;

</description>
      <category>testing</category>
    </item>
    <item>
      <title>A Decision Trace Must Distinguish False From Missing</title>
      <dc:creator>Arnold Holm</dc:creator>
      <pubDate>Thu, 17 Sep 2026 14:53:19 +0000</pubDate>
      <link>https://dev.to/stratcorealpha/a-decision-trace-must-distinguish-false-from-missing-2nci</link>
      <guid>https://dev.to/stratcorealpha/a-decision-trace-must-distinguish-false-from-missing-2nci</guid>
      <description>&lt;p&gt;A dashboard says WAIT. Was the spread check false, or did the engine never receive a spread observation?&lt;/p&gt;

&lt;p&gt;Those are different failures to investigate. A trace that turns both into an empty cell loses the distinction before debugging starts.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://www.reddit.com/r/cTrader_Club/comments/1whoy3m/what_do_you_think_of_this_decision_trace_for_a/" rel="noopener noreferrer"&gt;current discussion about trading-engine decision traces&lt;/a&gt; asks which evidence belongs beside a score and decision. My answer starts with a small requirement: preserve the inputs that actually existed at decision time, including explicit false values.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give missing data its own meaning
&lt;/h2&gt;

&lt;p&gt;For this example, the input contract has two boolean observations: &lt;code&gt;sessionOpen&lt;/code&gt; and &lt;code&gt;spreadAllowed&lt;/code&gt;. It is deliberately narrower than a real engine.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;false&lt;/code&gt; means the check ran and the condition was not met.&lt;/li&gt;
&lt;li&gt;A missing key means the observation was not recorded.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;null&lt;/code&gt; is invalid for this particular contract. A system that permits unavailable observations should give them an explicit status and reason.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not validate a boolean with &lt;code&gt;if (!value)&lt;/code&gt;. That rejects a perfectly legitimate false result. Check presence and type separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  A runnable boundary test
&lt;/h2&gt;

&lt;p&gt;Save the following as &lt;code&gt;trace.test.mjs&lt;/code&gt; and run &lt;code&gt;node --test trace.test.mjs&lt;/code&gt;. It uses Node's built-in test runner, with no packages or broker connection.&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:assert/strict&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;requireSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;decisionId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sourceEventId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;codeVersion&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;configVersion&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Missing &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;features&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;features&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Missing features&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sessionOpen&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;spreadAllowed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasOwn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;features&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;features&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;boolean&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid feature &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fixture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;decisionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;decision-example&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sourceEventId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;event-example&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;codeVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;example-build&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;configVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;example-config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;features&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;sessionOpen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;spreadAllowed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;false is recorded evidence, not missing data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;trace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;requireSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;an omitted feature is rejected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;trace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;features&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;spreadAllowed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;throws&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;requireSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sr"&gt;/Invalid feature spreadAllowed/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;null is not a boolean observation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;trace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;features&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;spreadAllowed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;throws&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;requireSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sr"&gt;/Invalid feature spreadAllowed/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a missing configuration version is rejected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;trace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;throws&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;requireSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sr"&gt;/Missing configVersion/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The four tests passed locally. That result covers this small validation function only. It does not establish the correctness of a trading strategy, broker integration or production trace pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the version fields do, and do not do
&lt;/h2&gt;

&lt;p&gt;The example also requires non-empty code and configuration version strings. An absent configuration identifier makes it hard to explain why the same event behaved differently after an input change.&lt;/p&gt;

&lt;p&gt;But a string is only a reference. This validator does not prove that the referenced build exists, that its contents match the identifier, or that the configuration was the one actually running. Production evidence needs those links to be checked and retained.&lt;/p&gt;

&lt;p&gt;I would store the input snapshot when the decision is made. Rebuilding it later from the latest chart or a revised data feed can silently describe a different decision.&lt;/p&gt;

&lt;p&gt;Keep the decision separate from its consequences too. A BUY decision, a submitted order request and an observed fill are three different facts. Join their records with identifiers; do not overwrite one status field until the earlier facts disappear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try the failure path
&lt;/h2&gt;

&lt;p&gt;Start with one saved test trace. Remove a required feature and confirm the reader rejects it. Restore the feature as false and confirm it survives unchanged. Then change the configuration reference and check that your investigation tools display the different version.&lt;/p&gt;

&lt;p&gt;This is a narrow test of evidence quality. A populated table is useful only if the values still mean what the engine meant when it wrote them.&lt;/p&gt;

&lt;p&gt;Reference: &lt;a href="https://nodejs.org/api/test.html" rel="noopener noreferrer"&gt;Node.js test runner&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>testing</category>
    </item>
    <item>
      <title>A Trading Restriction Needs an Owner: Three Tests for Account Scope</title>
      <dc:creator>Arnold Holm</dc:creator>
      <pubDate>Wed, 16 Sep 2026 18:37:33 +0000</pubDate>
      <link>https://dev.to/stratcorealpha/a-trading-restriction-needs-an-owner-three-tests-for-account-scope-2aji</link>
      <guid>https://dev.to/stratcorealpha/a-trading-restriction-needs-an-owner-three-tests-for-account-scope-2aji</guid>
      <description>&lt;p&gt;You move an instrument between two accounts. A restriction created for the source account follows the instrument into the destination account. The code finds the right symbol, returns a valid boolean, and still makes the wrong decision.&lt;/p&gt;

&lt;p&gt;I would test that ownership boundary before adding another trading rule. A correct symbol match does not establish which account a restriction belongs to.&lt;/p&gt;

&lt;p&gt;This post builds a small, copyable JavaScript test for that boundary. It does not connect to a broker, place an order, or reproduce a full trading system. Its claim is narrower: an account-specific restriction must not become a global restriction just because another account holds the same symbol.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the decision, not the database column
&lt;/h2&gt;

&lt;p&gt;The prompt for this example was &lt;a href="https://dev.to/finaltype/sep-11-a-do-not-rebuy-flag-that-force-sold-the-wrong-accounts-holdings-2d18"&gt;finaltype's public account-scope write-up&lt;/a&gt;. That discussion describes an exclusion applying beyond its intended account. The example below is independent; it is not a patch to that author's application.&lt;/p&gt;

&lt;p&gt;Imagine three accounts: &lt;code&gt;source&lt;/code&gt;, &lt;code&gt;destination&lt;/code&gt;, and &lt;code&gt;unrelated&lt;/code&gt;. All three may hold the instrument represented by the invented symbol &lt;code&gt;DEMO&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A user creates a do-not-rebuy flag for &lt;code&gt;source&lt;/code&gt;. The first question is not whether a row for &lt;code&gt;DEMO&lt;/code&gt; exists. It is whether that row applies to the account making the current decision.&lt;/p&gt;

&lt;p&gt;I would write the ownership rule like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flag for DEMO
    |
    +-- account scope: source
    |       +-- source      -&amp;gt; applies
    |       +-- destination -&amp;gt; does not apply
    |       +-- unrelated   -&amp;gt; does not apply
    |
    +-- explicit global scope
    |       +-- every account holding DEMO -&amp;gt; applies
    |
    +-- missing scope -&amp;gt; unresolved input, not a global default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the technical boundary. What an applicable flag actually does is a separate decision. Blocking a future purchase and requesting a sale are not interchangeable actions. This example deliberately produces neither.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the three acceptance cases visible
&lt;/h2&gt;

&lt;p&gt;Before the implementation, name the observations that would disprove it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Case&lt;/th&gt;
&lt;th&gt;Setup&lt;/th&gt;
&lt;th&gt;Required observation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Account isolation&lt;/td&gt;
&lt;td&gt;Restriction belongs to &lt;code&gt;source&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;It applies there, but not to &lt;code&gt;destination&lt;/code&gt; or &lt;code&gt;unrelated&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explicit global scope&lt;/td&gt;
&lt;td&gt;Restriction is deliberately global&lt;/td&gt;
&lt;td&gt;It applies across accounts for the matching symbol, but not another symbol&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Missing ownership&lt;/td&gt;
&lt;td&gt;A matching record has no scope, or an account flag has no account ID&lt;/td&gt;
&lt;td&gt;Evaluation reports unresolved scope rather than treating it as global&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The unrelated account matters. A test with only the source and destination can become tightly coupled to a particular transfer workflow. A third account checks the broader rule: sharing a symbol is not permission to share an account-specific decision.&lt;/p&gt;

&lt;p&gt;The different-symbol assertion matters too. A global restriction in this example is global across accounts, not across every instrument. Those are different meanings of the word global, and a test should force the distinction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Copy the complete fixture
&lt;/h2&gt;

&lt;p&gt;Save the following as &lt;code&gt;scope.test.mjs&lt;/code&gt;. It uses Node's built-in test runner and strict assertions, without dependencies or a broker connection.&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:assert/strict&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;applies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;global&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unresolved flag scope&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sourceFlag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DEMO&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;source&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;account restriction stays inside its account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;applies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceFlag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;source&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DEMO&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;applies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceFlag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;destination&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DEMO&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;applies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceFlag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unrelated&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DEMO&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;applies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceFlag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;source&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OTHER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;explicit global scope applies to the matching symbol&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;globalFlag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DEMO&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;global&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;applies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;globalFlag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;source&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DEMO&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;applies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;globalFlag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;destination&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DEMO&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;applies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;globalFlag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;source&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OTHER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;missing scope does not silently become global&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;throws&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;applies&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DEMO&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;source&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DEMO&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sr"&gt;/Unresolved flag scope/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;throws&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;applies&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DEMO&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;source&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DEMO&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sr"&gt;/Unresolved flag scope/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--test&lt;/span&gt; scope.test.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expected result is three passing tests. The &lt;a href="https://nodejs.org/api/test.html" rel="noopener noreferrer"&gt;Node test runner documentation&lt;/a&gt; explains the command and the &lt;code&gt;node:test&lt;/code&gt; module.&lt;/p&gt;

&lt;p&gt;For a useful negative control, replace the function body temporarily with &lt;code&gt;return flag.symbol === symbol&lt;/code&gt;. The account-isolation case and missing-scope case should fail. If your actual regression suite stays green after removing the ownership check, it is not protecting this boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Missing scope is a migration decision
&lt;/h2&gt;

&lt;p&gt;The example refuses to interpret an old, unscoped record as global. That is a design choice for this fixture, not a universal migration instruction.&lt;/p&gt;

&lt;p&gt;An existing product might have promised that every legacy restriction was global. If so, preserving that contract may be correct. Write the migration rule explicitly, label the converted records, and test the intended behavior before replacing the reader.&lt;/p&gt;

&lt;p&gt;What I would avoid is letting missing data make that decision accidentally. A nullable column, an empty string, and an explicit &lt;code&gt;global&lt;/code&gt; value should not quietly acquire the same meaning without a written rule.&lt;/p&gt;

&lt;p&gt;Also keep migration separate from evaluation. A migration can decide how an old record becomes a valid new record. The evaluator can then deal with a much smaller set of supported states. That separation makes the review easier: one change transforms stored meaning; another applies the agreed meaning.&lt;/p&gt;

&lt;h2&gt;
  
  
  A boolean is only one layer
&lt;/h2&gt;

&lt;p&gt;Passing these tests does not prove that a trading application is safe to run. The fixture assumes validated account and instrument identifiers. It does not resolve broker symbol aliases, verify user permissions, read a database, or test concurrent updates.&lt;/p&gt;

&lt;p&gt;In an application, the caller must handle unresolved scope as an explicit error state. Catching the exception and continuing as though no restriction exists would defeat the purpose. A visible hold with a diagnostic reason is one possible policy; the product needs to choose and test its own behavior.&lt;/p&gt;

&lt;p&gt;The storage path also needs coverage. If a query discards the account field before this function runs, the function cannot recover the lost information. Test the actual stored record, the query result, and the final decision together in an integration test.&lt;/p&gt;

&lt;p&gt;Finally, do not combine this check with broker-balance reconciliation. A restriction leaking across accounts and a ledger disagreeing with broker holdings are separate failures. Give each its own input, expected result, and evidence. Otherwise one passing assertion can hide which behavior was never exercised.&lt;/p&gt;

&lt;p&gt;The next useful step is small: find one account-scoped flag in your application, identify the writer and every reader, and add a test using the same symbol in an unrelated account. Keep the expected result beside the test. That makes ownership reviewable before the flag reaches an order-producing path.&lt;/p&gt;

</description>
      <category>testing</category>
    </item>
    <item>
      <title>Wrong Target, Clean Success: A Node Test for Misrouted Saves</title>
      <dc:creator>Arnold Holm</dc:creator>
      <pubDate>Wed, 09 Sep 2026 12:20:42 +0000</pubDate>
      <link>https://dev.to/stratcorealpha/wrong-target-clean-success-a-node-test-for-misrouted-saves-o7p</link>
      <guid>https://dev.to/stratcorealpha/wrong-target-clean-success-a-node-test-for-misrouted-saves-o7p</guid>
      <description>&lt;h2&gt;
  
  
  A dated defect worth learning from
&lt;/h2&gt;

&lt;p&gt;On 23 August 2026, &lt;a href="https://github.com/tradesdontlie/tradingview-mcp/issues/475" rel="noopener noreferrer"&gt;issue 475 in tradingview-mcp&lt;/a&gt; reported that &lt;code&gt;pine_new&lt;/code&gt; and &lt;code&gt;pine_open&lt;/code&gt; changed editor content without changing the saved script bound to it. A later save could therefore write to the wrong script. The report cites TradingView Desktop 3.3.0.7992 and Node 24.15.0. Its &lt;code&gt;pine_new&lt;/code&gt; observation stopped before a successful save, and the destructive &lt;code&gt;pine_open&lt;/code&gt; sequence was inferred from source rather than run to completion. This is dated motivation for a synthetic test, not a claim about today's software.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern
&lt;/h2&gt;

&lt;p&gt;The pattern is simple. A caller intends one record. The store has a different record active. A save follows the active binding instead of the intent. The response reports success. The intended record never changes. The active record loses its original text. Nobody gets a signal, because every message on the happy path looks correct until someone opens the wrong file.&lt;/p&gt;

&lt;p&gt;Two identities sit behind every save like this. The intended identity is the record the caller means to write. The active identity is the record the store currently has open. When the two differ, the write should stop before it starts. That is the whole idea, and the rest of this article is one small way to test it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the success message makes it worse
&lt;/h2&gt;

&lt;p&gt;The report raises another useful point: opening a script sounds like inspection. A caller may not expect it to alter an existing buffer. Yet a successful response can hide that distinction. The report also suggested a cheap remedy in the same spirit: have the save return the name and id of the script it actually wrote. A caller that sees the wrong name come back notices immediately. The check in this article moves that idea to the caller side, before the write instead of after it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The toy model
&lt;/h2&gt;

&lt;p&gt;The demonstration is synthetic and narrow on purpose. It models two saved scripts, A and B, as full records in a Map. Each record carries an id, a name, and source text. A naive save writes new source into whatever record is active, which is how the defect lives. A guarded save takes an intended id and an active id, validates both, and writes only when they match.&lt;/p&gt;

&lt;p&gt;I keep the checks strict in a specific way. Each case snapshots the full records with structuredClone before the operation, then compares the complete Map with assert.deepEqual after each rejected write (structuredClone is available in Node 17 and later). Checking one field would leave the rest of a record unverified, and a wrong claim could pass. After every rejected operation the test compares the complete Map against its snapshot, one rejection at a time. This also catches an unexpected added or deleted entry. The correct-target case checks the whole untouched B record the same way, so the untouched side is verified as a record, not as one field.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgkw2w9em8kktpwvebn6a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgkw2w9em8kktpwvebn6a.png" alt="synthetic A intended B active mismatch rejects and leaves both unchanged" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Standalone test program
&lt;/h2&gt;

&lt;p&gt;The program below is one file and depends only on node:assert/strict. Save it as check.mjs and run it with Node.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import assert from 'node:assert/strict';

function makeStore() {
  return new Map([
    ['A', { id: 'A', name: 'Script A', source: 'source A v1' }],
    ['B', { id: 'B', name: 'Script B', source: 'source B v1' }],
  ]);
}

// Full-record snapshot, so comparisons cover every field.
function snapshot(store) {
  const out = new Map();
  for (const [key, record] of store) out.set(key, structuredClone(record));
  return out;
}

function naiveSave(store, activeId, newSource) {
  const record = store.get(activeId);
  if (!record) throw new Error('missing id');
  record.source = newSource;
  return activeId;
}

function guardedSave(store, intendedId, activeId, newSource) {
  if (typeof intendedId !== 'string' || intendedId.length === 0) {
    throw new Error('invalid intended id');
  }
  if (typeof activeId !== 'string' || activeId.length === 0) {
    throw new Error('invalid active id');
  }
  if (intendedId !== activeId) throw new Error('wrong target');
  const record = store.get(intendedId);
  if (!record) throw new Error('missing id');
  record.source = newSource;
  return intendedId;
}

// Case 1: naive misroute, the defect demonstrated.
{
  const store = makeStore();
  const before = snapshot(store);
  assert.notEqual('A', 'B');
  const out = naiveSave(store, 'B', 'source A v2');
  assert.equal(out, 'B');
  assert.deepEqual(store.get('A'), before.get('A'));
  assert.deepEqual(store.get('B'), { id: 'B', name: 'Script B', source: 'source A v2' });
  console.log('case 1 naive misroute: B holds A v2, A record unchanged');
}

// Case 2: mismatch rejected, both full records unchanged.
{
  const store = makeStore();
  const before = snapshot(store);
  assert.throws(() =&amp;gt; guardedSave(store, 'A', 'B', 'source A v2'), /wrong target/);
  assert.deepEqual(store, before);
  console.log('case 2 guarded mismatch: rejected, both records unchanged');
}

// Case 3: correct target, A updated, whole B record untouched.
{
  const store = makeStore();
  const before = snapshot(store);
  const out = guardedSave(store, 'A', 'A', 'source A v2');
  assert.equal(out, 'A');
  assert.deepEqual(store.get('A'), { id: 'A', name: 'Script A', source: 'source A v2' });
  assert.deepEqual(store.get('B'), before.get('B'));
  console.log('case 3 correct target: A updated, B record unchanged');
}

// Case 4: invalid and missing identities, checked after every rejection.
{
  const store = makeStore();
  const before = snapshot(store);
  assert.throws(() =&amp;gt; guardedSave(store, '', 'A', 'x'), /invalid intended id/);
  assert.deepEqual(store, before);
  assert.throws(() =&amp;gt; guardedSave(store, 'A', '', 'x'), /invalid active id/);
  assert.deepEqual(store, before);
  assert.throws(() =&amp;gt; guardedSave(store, 'Z', 'Z', 'x'), /missing id/);
  assert.deepEqual(store, before);
  console.log('case 4 invalid ids: three rejections, no writes');
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What each case checks
&lt;/h2&gt;

&lt;p&gt;Case 1 keeps the naive misroute on display. The intended record is A, the active record is B, and the naive save writes A's new text into B. The test asserts B now holds A's text while A keeps its original full record, which is the defect stated as a fact.&lt;/p&gt;

&lt;p&gt;Case 2 sends the same mismatch through the guarded save. The guard throws wrong target, and the test compares both full records against their snapshots. Nothing was written.&lt;/p&gt;

&lt;p&gt;Case 3 matches intended and active on A. The guard writes, A's full record shows the new source, and the whole B record stays equal to its snapshot.&lt;/p&gt;

&lt;p&gt;Case 4 feeds three bad identities: an empty intended id, an empty active id, and a missing id Z. Every rejection is followed by a full-record comparison, so a partial write cannot hide between attempts.&lt;/p&gt;

&lt;p&gt;Each case group prints one line after its assertions pass. An assertion failure stops the program before that line. The run record below contains the observed output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test table
&lt;/h2&gt;

&lt;p&gt;The table states the contract the asserts enforce. Expected mutation means what the store should look like after the operation.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Expected mutation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Naive misroute&lt;/td&gt;
&lt;td&gt;intended A, active B, naive save with A v2 source&lt;/td&gt;
&lt;td&gt;B record rewritten to hold A v2; A record unchanged; B v1 text gone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Guarded mismatch&lt;/td&gt;
&lt;td&gt;intended A, active B, guarded save&lt;/td&gt;
&lt;td&gt;no mutation; both full records equal their snapshots; wrong target error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Correct target&lt;/td&gt;
&lt;td&gt;intended A, active A, guarded save&lt;/td&gt;
&lt;td&gt;A record updated to v2 source; whole B record unchanged&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Invalid intended id&lt;/td&gt;
&lt;td&gt;intended empty string, active A&lt;/td&gt;
&lt;td&gt;no mutation; both full records equal snapshots; invalid id error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Invalid active id&lt;/td&gt;
&lt;td&gt;intended A, active empty string&lt;/td&gt;
&lt;td&gt;no mutation; both full records equal snapshots; invalid id error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Missing record&lt;/td&gt;
&lt;td&gt;intended Z, active Z&lt;/td&gt;
&lt;td&gt;no mutation; both full records equal snapshots; missing id error&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Run status
&lt;/h2&gt;

&lt;p&gt;I ran the exact listing with &lt;code&gt;node check.mjs&lt;/code&gt; on Node v23.6.1 on 9 September 2026. The process exited with code 0 and printed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;case 1 naive misroute: B holds A v2, A record unchanged
case 2 guarded mismatch: rejected, both records unchanged
case 3 correct target: A updated, B record unchanged
case 4 invalid ids: three rejections, no writes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where this check stops
&lt;/h2&gt;

&lt;p&gt;The model is synchronous, single-threaded, and in memory. It shows the guard catching a mismatch inside one toy store, and nothing beyond that. It is not evidence about async flows, races between operations, atomicity of a check plus a write, persistence, or concurrent writers. It is not a real editor test. No TradingView component, MCP server, or Monaco editor is involved anywhere in the listing.&lt;/p&gt;

&lt;p&gt;The gap matters. An explicit intended-id guard in a synchronous model is not sufficient for async persistence. Between the check and the write, the active binding can change, or a second writer can commit first. The comparison has to happen at the single point where the store persists, as an atomic conditional write or a version check that accepts or rejects the whole operation together. That is where a real fix belongs. This article names that place and does not claim any implementation of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reuse it
&lt;/h2&gt;

&lt;p&gt;The useful part is the shape of the test. Snapshot whole records before the operation. Reject on identity mismatch before any write. Compare full records after every rejection, one rejection at a time. Keep the naive path in case 1 so the failure stays visible next to the fix. Then change names, fields, and store to match your own system. A toy that runs in seconds and states its own limits is easier to trust than a promise that never shows its checks.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>testing</category>
      <category>security</category>
    </item>
    <item>
      <title>A Paired Buy and Sell EA Needs One Explicit Cycle State</title>
      <dc:creator>Arnold Holm</dc:creator>
      <pubDate>Wed, 02 Sep 2026 12:34:42 +0000</pubDate>
      <link>https://dev.to/stratcorealpha/a-paired-buy-and-sell-ea-needs-one-explicit-cycle-state-1l37</link>
      <guid>https://dev.to/stratcorealpha/a-paired-buy-and-sell-ea-needs-one-explicit-cycle-state-1l37</guid>
      <description>&lt;p&gt;A paired buy and sell EA can open both legs correctly and still fail after the first exit. The reason is usually not the entry signal. The lifecycle was never defined.&lt;/p&gt;

&lt;p&gt;That distinction matters to a buyer because a clean compile only proves that the code is syntactically valid. It does not prove that the Expert Advisor knows which cycle is active, which leg survived, or whether a restart permits another entry.&lt;/p&gt;

&lt;h2&gt;
  
  
  The buyer question
&lt;/h2&gt;

&lt;p&gt;Before development starts, the specification should answer one question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What state is the EA in after one leg closes, and what exact event completes the cycle?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If that answer is vague, the implementation can place a replacement order too early, leave an orphaned position unmanaged, or restart into the wrong state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use one explicit lifecycle
&lt;/h2&gt;

&lt;p&gt;A practical model has four states:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;IDLE&lt;/strong&gt;: no active pair exists and a new cycle may start.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PAIR_OPEN&lt;/strong&gt;: both legs belong to the active cycle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ONE_LEG_CLOSED&lt;/strong&gt;: one leg has exited and the remaining leg still belongs to the same cycle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CYCLE_COMPLETE&lt;/strong&gt;: both legs are closed, cleanup is complete, and the EA may return to IDLE.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The names can change. The important part is that every transition has one deterministic trigger.&lt;/p&gt;

&lt;p&gt;For example, the transition from &lt;code&gt;PAIR_OPEN&lt;/code&gt; to &lt;code&gt;ONE_LEG_CLOSED&lt;/code&gt; should be caused by one verified close event for a ticket that belongs to the active cycle. It should not be inferred only from a temporary order count. The transition from &lt;code&gt;ONE_LEG_CLOSED&lt;/code&gt; to &lt;code&gt;CYCLE_COMPLETE&lt;/code&gt; should require proof that the survivor has closed and that no cycle-owned pending order remains.&lt;/p&gt;

&lt;h2&gt;
  
  
  A minimal transition table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Current state&lt;/th&gt;
&lt;th&gt;Verified event&lt;/th&gt;
&lt;th&gt;Next state&lt;/th&gt;
&lt;th&gt;Forbidden side effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;IDLE&lt;/td&gt;
&lt;td&gt;entry condition and no active cycle&lt;/td&gt;
&lt;td&gt;PAIR_OPEN&lt;/td&gt;
&lt;td&gt;opening two cycles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PAIR_OPEN&lt;/td&gt;
&lt;td&gt;one cycle-owned leg closes&lt;/td&gt;
&lt;td&gt;ONE_LEG_CLOSED&lt;/td&gt;
&lt;td&gt;replacing the closed leg&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ONE_LEG_CLOSED&lt;/td&gt;
&lt;td&gt;survivor closes&lt;/td&gt;
&lt;td&gt;CYCLE_COMPLETE&lt;/td&gt;
&lt;td&gt;opening a new pair twice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CYCLE_COMPLETE&lt;/td&gt;
&lt;td&gt;cleanup persisted&lt;/td&gt;
&lt;td&gt;IDLE&lt;/td&gt;
&lt;td&gt;losing the completed-cycle record&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This table gives a buyer something more useful than “the bot works.” It defines what can be checked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Freeze three acceptance tests
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Normal closure
&lt;/h3&gt;

&lt;p&gt;Both entries fill. Both legs close through their intended exit rules. The EA records the cycle as complete and does not open a replacement until the next valid cycle trigger.&lt;/p&gt;

&lt;p&gt;The evidence should include the two entry tickets, both close events, the single cycle identifier, and the final state.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. One leg closes first
&lt;/h3&gt;

&lt;p&gt;One position hits its stop, target, or included manual-close condition. The surviving leg remains managed by the current cycle. The EA must not treat the account as flat and must not create a duplicate pair.&lt;/p&gt;

&lt;p&gt;This is where many apparently simple paired-order requests become ambiguous. “Keep the other trade running” is not enough. The specification still needs to say which management rules remain active and whether another signal is ignored until the survivor closes.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Terminal restart
&lt;/h3&gt;

&lt;p&gt;MetaTrader restarts while the pair is open or while one leg remains. The EA reconstructs the same state from durable identifiers and current positions. It must not depend only on variables that disappeared with the terminal session.&lt;/p&gt;

&lt;p&gt;A useful restart test records the state before shutdown, starts the terminal again, and compares the reconstructed cycle identifier, leg ownership and permission to open the next pair. Any difference is a failed acceptance case.&lt;/p&gt;

&lt;h2&gt;
  
  
  The identifiers matter
&lt;/h2&gt;

&lt;p&gt;A stable implementation should be able to answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which positions belong to the current cycle?&lt;/li&gt;
&lt;li&gt;Which leg is the buy and which is the sell?&lt;/li&gt;
&lt;li&gt;Has either leg already closed?&lt;/li&gt;
&lt;li&gt;Was the close expected, manual, or caused by a broker event?&lt;/li&gt;
&lt;li&gt;Is a new cycle allowed now?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Magic numbers, symbols, comments, ticket mappings and persisted cycle identifiers can all help. The correct combination depends on the platform and scope. What matters is that the reconstruction rule is explicit and testable.&lt;/p&gt;

&lt;p&gt;In MT4, order-history scans also need a fixed selection rule. “Take the last closed order” can be wrong when another EA or a manual trade uses the same account. In MT5 hedging mode, position and deal history introduce a different mapping problem. Platform choice is part of the acceptance contract, not a detail to leave until coding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Warning signs in a specification
&lt;/h2&gt;

&lt;p&gt;The following phrases usually need clarification before coding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Open a new pair when one trade closes.”&lt;/li&gt;
&lt;li&gt;“Keep the other trade running as normal.”&lt;/li&gt;
&lt;li&gt;“Restart the bot after both trades finish.”&lt;/li&gt;
&lt;li&gt;“Avoid duplicate orders.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each phrase hides multiple state transitions. Does a partial close count? What happens after a manual close? What if the broker rejects one entry? What if the terminal restarts between the close event and the state update?&lt;/p&gt;

&lt;p&gt;These are not reasons to delay the project. They are decisions that can be frozen as safe defaults. For a first version, a reasonable boundary might reject partial closes, keep the survivor under its original stop and target, and permit the next cycle only after both legs and all cycle-owned pending orders are gone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope boundaries prevent revision loops
&lt;/h2&gt;

&lt;p&gt;A clean first version should state which close paths are included. It should also list exclusions such as partial closes, cross-symbol baskets, manual intervention, broker-side order changes, or recovery from edited comments if those cases are not part of the initial scope.&lt;/p&gt;

&lt;p&gt;That is not bureaucracy. It gives the buyer and developer the same definition of “done.” A revision is then about a failed agreed case, not a new interpretation of an undefined lifecycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  What proof should be delivered?
&lt;/h2&gt;

&lt;p&gt;For this kind of EA, a useful handover contains more than the &lt;code&gt;.mq4&lt;/code&gt; or &lt;code&gt;.mq5&lt;/code&gt; file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;editable source code;&lt;/li&gt;
&lt;li&gt;a clean compile result;&lt;/li&gt;
&lt;li&gt;the transition table used by the implementation;&lt;/li&gt;
&lt;li&gt;logs or screenshots for the three acceptance cases;&lt;/li&gt;
&lt;li&gt;a short change note;&lt;/li&gt;
&lt;li&gt;setup notes covering platform mode, symbol and identifiers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The source lets the buyer retain control. The matrix makes the behavior reviewable. The evidence separates “compiled” from “accepted.”&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple pre-coding checklist
&lt;/h2&gt;

&lt;p&gt;Before funding implementation, freeze:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the four lifecycle states;&lt;/li&gt;
&lt;li&gt;every allowed transition;&lt;/li&gt;
&lt;li&gt;the identifier used to rebuild state;&lt;/li&gt;
&lt;li&gt;the three acceptance tests above;&lt;/li&gt;
&lt;li&gt;the included close paths;&lt;/li&gt;
&lt;li&gt;the excluded edge cases;&lt;/li&gt;
&lt;li&gt;the exact event that allows the next cycle.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once those points are deterministic, the entry and exit rules become much easier to implement and test.&lt;/p&gt;

&lt;p&gt;The full buyer checklist is available here: &lt;a href="https://stratcorealpha.com/engineering-notes/mt4-paired-order-lifecycle" rel="noopener noreferrer"&gt;https://stratcorealpha.com/engineering-notes/mt4-paired-order-lifecycle&lt;/a&gt;&lt;/p&gt;

</description>
      <category>automation</category>
      <category>testing</category>
      <category>programming</category>
    </item>
    <item>
      <title>Eight Session-Boundary Tests I Run Before Trusting a Trading Bot</title>
      <dc:creator>Arnold Holm</dc:creator>
      <pubDate>Sat, 15 Aug 2026 01:56:19 +0000</pubDate>
      <link>https://dev.to/stratcorealpha/eight-session-boundary-tests-i-run-before-trusting-a-trading-bot-ofj</link>
      <guid>https://dev.to/stratcorealpha/eight-session-boundary-tests-i-run-before-trusting-a-trading-bot-ofj</guid>
      <description>&lt;p&gt;A session reset looks simple until a platform reloads data, reconnects or sends the same timestamp twice.&lt;/p&gt;

&lt;p&gt;If a bot resets state from the calendar date alone, a restart can unlock a risk control early. Or the same session can reset twice. I keep two facts separate before I touch entry logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a new bar&lt;/li&gt;
&lt;li&gt;a new trading session&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most new bars are not session starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small classifier first
&lt;/h2&gt;

&lt;p&gt;The first pass can be read-only. It receives the current timestamp and the platform's native session marker. It returns one observation class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Observation&lt;/span&gt; &lt;span class="nf"&gt;Classify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;nativeSessionStart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;hasPrevious&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;hasPrevious&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Observation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;First&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Observation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reordered&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;nativeSessionStart&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Observation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DuplicateSessionMarker&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Observation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duplicate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;nativeSessionStart&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Observation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewBarAndSession&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Observation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewBar&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does not decide when trading is allowed. It only makes platform events observable. The business reset stays separate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The eight tests
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;First observation.&lt;/strong&gt; Initialize without inventing a previous bar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forward bar.&lt;/strong&gt; Advance once without a session event.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate update.&lt;/strong&gt; Keep state when the timestamp has not changed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate session marker.&lt;/strong&gt; Suppress the same marker at the same timestamp.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reordered data.&lt;/strong&gt; Report a backward timestamp instead of treating it as forward.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reordered session marker.&lt;/strong&gt; Reject the marker when its timestamp moves backward.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native session start.&lt;/strong&gt; Report it exactly once on the platform signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explicit reset.&lt;/strong&gt; Clear observation state without claiming that the market began a new session.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Put restart behavior in the contract
&lt;/h2&gt;

&lt;p&gt;A pure classifier is only the start. If the bot carries a daily loss lock, trade counter or opening range across bars, choose the recovery model before implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;restore the last trusted state from persistent storage;&lt;/li&gt;
&lt;li&gt;rebuild it from authorized account and chart history;&lt;/li&gt;
&lt;li&gt;stay locked until the next verified native session boundary;&lt;/li&gt;
&lt;li&gt;require an explicit operator decision in a demo or test environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Leaving this undefined is what creates restart-only bugs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the evidence packet small
&lt;/h2&gt;

&lt;p&gt;For each run I record:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;source hash and platform version;&lt;/li&gt;
&lt;li&gt;instrument, bar type and trading-hours template;&lt;/li&gt;
&lt;li&gt;ordered timestamps and native session markers;&lt;/li&gt;
&lt;li&gt;expected and observed class for each test;&lt;/li&gt;
&lt;li&gt;reload and restart result;&lt;/li&gt;
&lt;li&gt;what was not tested.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is enough to separate a code defect from a chart-template mismatch. It also keeps a later test from quietly using different source or settings.&lt;/p&gt;

&lt;p&gt;The business rule should still fit in one sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Reset &lt;code&gt;named state&lt;/code&gt; when &lt;code&gt;observable event&lt;/code&gt; occurs, provided &lt;code&gt;state condition&lt;/code&gt;; otherwise keep the prior state until &lt;code&gt;fallback event&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then add one case where the reset must happen and one where it must not.&lt;/p&gt;

&lt;p&gt;For the longer reasoning behind calendar midnight and native session events, see &lt;a href="https://medium.com/@stratcorealpha/a-trading-session-is-an-event-not-just-a-date-c3ffd3b2668f" rel="noopener noreferrer"&gt;A Trading Session Is an Event, Not Just a Date&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is about software behavior and testing. It is not investment advice and does not promise profitability or trading performance.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>csharp</category>
      <category>automation</category>
      <category>dotnet</category>
    </item>
  </channel>
</rss>
