<?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: Mike Clarke</title>
    <description>The latest articles on DEV Community by Mike Clarke (@mike_clarke_50a95013f5c59).</description>
    <link>https://dev.to/mike_clarke_50a95013f5c59</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%2F3868865%2Ff368d9be-f55c-4ab9-a26e-a73625709b2b.jpg</url>
      <title>DEV Community: Mike Clarke</title>
      <link>https://dev.to/mike_clarke_50a95013f5c59</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mike_clarke_50a95013f5c59"/>
    <language>en</language>
    <item>
      <title>The cleanup code that ate every code block in our published articles</title>
      <dc:creator>Mike Clarke</dc:creator>
      <pubDate>Tue, 04 Aug 2026 14:00:21 +0000</pubDate>
      <link>https://dev.to/mike_clarke_50a95013f5c59/the-cleanup-code-that-ate-every-code-block-in-our-published-articles-5620</link>
      <guid>https://dev.to/mike_clarke_50a95013f5c59/the-cleanup-code-that-ate-every-code-block-in-our-published-articles-5620</guid>
      <description>&lt;p&gt;Liquid syntax error: Variable '{{% raw %}' was not properly terminated with regexp: /\}\}/&lt;/p&gt;
</description>
      <category>programming</category>
      <category>coding</category>
      <category>webdev</category>
      <category>learning</category>
    </item>
    <item>
      <title>Smart Traffic Systems: Configuring Flow for a Smarter City</title>
      <dc:creator>Mike Clarke</dc:creator>
      <pubDate>Wed, 29 Jul 2026 06:00:20 +0000</pubDate>
      <link>https://dev.to/mike_clarke_50a95013f5c59/smart-traffic-systems-configuring-flow-for-a-smarter-city-odd</link>
      <guid>https://dev.to/mike_clarke_50a95013f5c59/smart-traffic-systems-configuring-flow-for-a-smarter-city-odd</guid>
      <description>&lt;h2&gt;
  
  
  Smart Traffic Systems: Configuring Flow for a Smarter City
&lt;/h2&gt;

&lt;p&gt;Ever found yourself stuck in a gridlocked intersection, wondering why the lights aren't cooperating? Or perhaps you've mused about the sheer inefficiency of fixed-timer traffic signals in a dynamic urban landscape. As developers, these aren't just annoyances; they're ripe problems begging for intelligent solutions. And that's where smart traffic systems come in.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem: Static Systems vs. Dynamic Reality
&lt;/h3&gt;

&lt;p&gt;Traditional traffic light systems operate on pre-programmed timings. They don't care if an ambulance needs to pass, if a bus is running behind schedule, or if one lane is bumper-to-bumper while another is empty. This static approach leads to congestion, increased travel times, higher fuel consumption, and crucially, frustrated citizens. We can do better.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Concept: Dynamic, Data-Driven Control
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;How to configure a smart traffic system?&lt;/strong&gt; At its core, it's about shifting from reactive, static timing to proactive, data-driven optimization. A smart system continuously gathers data from various sources: loop detectors, cameras (for vehicle count and classification), GPS data from connected vehicles, even weather sensors. This data feeds into an intelligent agent, often leveraging AI/ML algorithms, to make real-time decisions about traffic light signalization, lane usage, pedestrian crossings, and even suggesting alternative routes.&lt;/p&gt;

&lt;p&gt;Think of it as a central brain that constantly analyzes the pulse of the city's arteries and adjusts flow accordingly. The goal is to minimize overall travel time, reduce congestion, prioritize emergency vehicles, and optimize public transport efficiency. It's a complex optimization problem, often involving multi-agent systems and reinforcement learning.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Glimpse Under the Hood (Pseudocode):
&lt;/h3&gt;

&lt;p&gt;Let's imagine a simplified scenario for a single intersection. Our smart system needs to decide which phase (e.g., North-South green, East-West red) to activate and for how long.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Initialize system components
TrafficLightController = new Controller()
SensorDataStream = new DataStreamService()
OptimalPhaseSelector = new MLModel()

// Main Loop - runs continuously
LOOP:
    // 1. Gather Real-time Data
    currentTrafficData = SensorDataStream.getLiveTrafficData(intersectionID)
    // currentTrafficData might include:
    //    - VehicleCountsPerLane
    //    - AverageWaitingTimesPerLane
    //    - PresenceOfEmergencyVehicles
    //    - PedestrianCrossingRequests

    // 2. Process Data and Determine Optimal Phase
    optimalPhaseResult = OptimalPhaseSelector.predictOptimalPhase(
        currentTrafficData,
        historicalTrafficPatterns,
        currentWeatherConditions
    )
    // optimalPhaseResult might contain:
    //    - recommendedPhase (e.g., 'NS_GREEN')
    //    - recommendedDuration (e.g., 45 seconds)
    //    - priorityFlags (e.g., 'EmergencyVehiclePresent')

    // 3. Command Traffic Lights
    IF optimalPhaseResult.priorityFlags.contains('EmergencyVehiclePresent'):
        TrafficLightController.triggerEmergencyPhase(optimalPhaseResult.recommendedPhase)
    ELSE:
        TrafficLightController.setPhase(optimalPhaseResult.recommendedPhase, optimalPhaseResult.recommendedDuration)

    // 4. Log and Monitor (for debugging and retraining)
    Log.event("Phase changed to " + optimalPhaseResult.recommendedPhase + " for " + optimalPhaseResult.recommendedDuration)
    Monitor.displayCurrentTrafficFlow()

    WAIT for short interval // Re-evaluate every few seconds
END LOOP

// --- Additional functions for OptimalPhaseSelector (simplified) ---

FUNCTION predictOptimalPhase(data, history, weather):
    // Example logic (highly simplified):
    IF data.EmergencyVehicles &amp;gt; 0:
        RETURN {recommendedPhase: 'ClearPathForEmergency', recommendedDuration: 10, priorityFlags: ['EmergencyVehiclePresent']}

    ELSE IF data.LaneTraffic['NorthBound'] &amp;gt; data.LaneTraffic['EastBound'] * 2 AND data.WaitingTimes['NorthBound'] &amp;gt; 60:
        RETURN {recommendedPhase: 'NS_GREEN', recommendedDuration: 60}

    ELSE IF data.PedestrianRequests['EastCross'] AND data.SafeToCross:
        RETURN {recommendedPhase: 'EW_PED_GREEN', recommendedDuration: 20}

    ELSE:
        // Fallback to a default or historical pattern if no strong signal
        RETURN {recommendedPhase: history.getDefaultPhaseForTimeOfDay(), recommendedDuration: 30}
END FUNCTION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pseudocode barely scratches the surface. A real-world system would involve complex state machines, robust fault tolerance, distributed sensing, and sophisticated machine learning models trained on vast datasets. The &lt;code&gt;OptimalPhaseSelector&lt;/code&gt; would likely be a black box of intricate algorithms deciding based on predicted future states, not just current ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Practice Matters in This Domain
&lt;/h3&gt;

&lt;p&gt;Configuring a smart traffic system isn't just about writing code; it's about understanding complex systems, optimizing for multiple conflicting objectives, and handling real-time data under pressure. There are countless edge cases, from sensor failures to unexpected traffic surges. Practicing these concepts – building simplified models, simulating various scenarios, and tweaking parameters – is crucial for developing the intuition needed to build robust, life-changing solutions.&lt;/p&gt;

&lt;p&gt;It's one thing to read about Reinforcement Learning; it's another to apply it to make a simulated city's traffic flow smoother. These are problems where your algorithms have a direct, tangible impact.&lt;/p&gt;

&lt;p&gt;Practice this concept interactively on CodeCityApp — free trial at &lt;a href="http://codecityapp.com" rel="noopener noreferrer"&gt;codecityapp.com&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://codecityapp.com" rel="noopener noreferrer"&gt;CodeCityApp&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>coding</category>
      <category>programming</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The upsert said it worked. It wrote zero rows. Every single run.</title>
      <dc:creator>Mike Clarke</dc:creator>
      <pubDate>Tue, 28 Jul 2026 14:00:04 +0000</pubDate>
      <link>https://dev.to/mike_clarke_50a95013f5c59/the-upsert-said-it-worked-it-wrote-zero-rows-every-single-run-47e1</link>
      <guid>https://dev.to/mike_clarke_50a95013f5c59/the-upsert-said-it-worked-it-wrote-zero-rows-every-single-run-47e1</guid>
      <description>&lt;p&gt;Your scanner runs. It fetches data. It loops through rows, calls upsert, increments a success counter. It logs &lt;code&gt;✓ 42 rows processed&lt;/code&gt;. You check the table. Empty.&lt;/p&gt;

&lt;p&gt;That ran every day for a while before we caught it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ON CONFLICT (col)&lt;/code&gt; will &lt;strong&gt;not&lt;/strong&gt; use a partial unique index (&lt;code&gt;WHERE col IS NOT NULL&lt;/code&gt;) as the conflict arbiter. Postgres requires the predicate to be restated in the statement itself.&lt;/li&gt;
&lt;li&gt;PostgREST's upsert path can't restate that predicate. Every row throws a quiet error.&lt;/li&gt;
&lt;li&gt;A loop that only increments on success will report a perfectly healthy run that wrote absolutely nothing.&lt;/li&gt;
&lt;li&gt;Fix: replace the partial index with a plain, unconditional unique index.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;At Elevare Digital, ARIA runs data ingestion pipelines that pull from external sources and upsert into Postgres via Supabase. Standard stuff. One scanner's job was to fetch records and land them in a table, using a unique column as the conflict target so re-runs were idempotent.&lt;/p&gt;

&lt;p&gt;The index on that column had been created like this, probably by someone being careful about NULLs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;records_external_id_idx&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;external_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;external_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reasonable-looking. Partial indexes are useful. This one quietly broke everything.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Postgres actually requires
&lt;/h2&gt;

&lt;p&gt;When you write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;external_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'abc-123'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'{...}'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;CONFLICT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;external_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;
  &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EXCLUDED&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Postgres needs to identify which unique constraint or index to use as the arbiter. For a &lt;strong&gt;full&lt;/strong&gt; unique index, &lt;code&gt;ON CONFLICT (external_id)&lt;/code&gt; is enough — Postgres finds it.&lt;/p&gt;

&lt;p&gt;For a &lt;strong&gt;partial&lt;/strong&gt; unique index, Postgres requires the conflict target to include the index predicate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;external_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'abc-123'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'{...}'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;CONFLICT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;external_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;external_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;  &lt;span class="c1"&gt;-- restate the predicate&lt;/span&gt;
&lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;
  &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EXCLUDED&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without that &lt;code&gt;WHERE&lt;/code&gt; clause, Postgres cannot match the partial index. It doesn't fall back. It throws an error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can verify this yourself in a few lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Create a table with a partial unique index&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;demo&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;serial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;external_id&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;demo_ext_id_partial&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;demo&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;external_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;external_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- This fails — Postgres can't match the partial index&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;demo&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;external_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'x1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;CONFLICT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;external_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EXCLUDED&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification&lt;/span&gt;

&lt;span class="c1"&gt;-- This works — predicate restated&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;demo&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;external_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'x1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;CONFLICT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;external_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;external_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EXCLUDED&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- INSERT 0 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why PostgREST couldn't restate the predicate
&lt;/h2&gt;

&lt;p&gt;ARIA calls Supabase's upsert via PostgREST. The request looks something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;records&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="nf"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;onConflict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;external_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgREST translates &lt;code&gt;onConflict: 'external_id'&lt;/code&gt; into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;CONFLICT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;external_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's no interface in PostgREST's upsert path to append an arbitrary &lt;code&gt;WHERE&lt;/code&gt; predicate to the conflict target. So every row-level upsert hit the same error. Every row.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the loop reported success
&lt;/h2&gt;

&lt;p&gt;The ingestion loop looked roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;successCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;row&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;fetchedRows&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;records&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="nf"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;onConflict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;external_id&lt;/span&gt;&lt;span class="dl"&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;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;successCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// error case: nothing. no log, no throw, no increment.&lt;/span&gt;
  &lt;span class="p"&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;log&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;successCount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; rows processed`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;code&gt;error&lt;/code&gt; was non-null, the loop just... continued. No log. No counter. The success counter never moved, but &lt;code&gt;fetchedRows.length&lt;/code&gt; rows later, it logged a number that looked fine because the fetch itself succeeded.&lt;/p&gt;

&lt;p&gt;Actually — it logged &lt;code&gt;0&lt;/code&gt;. But nobody was watching for zero. Zero looks like "nothing to sync today."&lt;/p&gt;

&lt;p&gt;A counter that only moves on success will always look plausible. You need to also track failures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;successCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;errorCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;row&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;fetchedRows&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;records&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="nf"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;onConflict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;external_id&lt;/span&gt;&lt;span class="dl"&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;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;errorCount&lt;/span&gt;&lt;span class="o"&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;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;upsert failed:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;external_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;successCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`processed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;successCount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ok, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;errorCount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; failed of &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fetchedRows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; fetched`&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;errorCount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&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="s2"&gt;`ingestion completed with &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;errorCount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; errors`&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;Now a run that writes nothing will at least be loud about it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Drop the partial index. Create a plain one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;records_external_id_idx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;records_external_id_idx&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;external_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If NULLs are genuinely a concern, handle them at the application layer or with a &lt;code&gt;NOT NULL&lt;/code&gt; constraint on the column. A partial index to "exclude NULLs" is not worth the silent failure mode when you need conflict arbitration.&lt;/p&gt;

&lt;p&gt;After the index swap, the PostgREST upsert matched correctly and rows started landing.&lt;/p&gt;




&lt;h2&gt;
  
  
  The honest lesson
&lt;/h2&gt;

&lt;p&gt;Postgres did nothing wrong here. The docs describe this requirement. The error message is clear. The problem was that the error was being swallowed and nothing downstream cared that zero rows were written.&lt;/p&gt;

&lt;p&gt;Two independent things had to both be true for this to stay hidden:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A partial index being used as an &lt;code&gt;ON CONFLICT&lt;/code&gt; target without the predicate&lt;/li&gt;
&lt;li&gt;A loop that treated errors as a no-op&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Either one alone might have surfaced faster. Together, they produced a system that appeared healthy and did nothing.&lt;/p&gt;

&lt;p&gt;When you write an ingestion loop, the number that matters isn't "how many did I try" or "how many succeeded." It's the ratio. And if errors are silent, you'll never compute it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;— Mike Clarke, founder of Elevare Digital.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>coding</category>
      <category>webdev</category>
      <category>learning</category>
    </item>
    <item>
      <title>The leads were in the table. The automation never saw them.</title>
      <dc:creator>Mike Clarke</dc:creator>
      <pubDate>Fri, 24 Jul 2026 14:00:12 +0000</pubDate>
      <link>https://dev.to/mike_clarke_50a95013f5c59/the-leads-were-in-the-table-the-automation-never-saw-them-24o</link>
      <guid>https://dev.to/mike_clarke_50a95013f5c59/the-leads-were-in-the-table-the-automation-never-saw-them-24o</guid>
      <description>&lt;p&gt;There were two groups of leads sitting in &lt;code&gt;contacted&lt;/code&gt; state. One group was getting follow-up emails, call tasks, the whole sequence. The other group was getting nothing — indefinitely.&lt;/p&gt;

&lt;p&gt;Same status. Same table. Different outcomes. No errors anywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Takeaways&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A "dead population path" is when one write route into a table skips a step that downstream automation assumes happened.&lt;/li&gt;
&lt;li&gt;Bulk imports are the most common source of this. They write directly to the final state, skipping every intermediate code path.&lt;/li&gt;
&lt;li&gt;The fix is not patching the import. It's a trigger that enforces enrollment regardless of how a row arrives.&lt;/li&gt;
&lt;li&gt;For every table your automation reads: list every way rows enter it, then verify each path runs the prerequisites downstream depends on.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What was actually happening
&lt;/h2&gt;

&lt;p&gt;The CRM pipeline in ARIA works like this: a lead gets contacted, the first-contact handler fires, it enrolls the lead in the follow-up sequence. That enrollment step lived exclusively inside the first-contact code path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User action / API call
  → first-contact handler
      → write lead to `contacted` state
      → enroll in follow-up sequence  ← this is the step
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Someone ran a bulk import. It wrote records straight to &lt;code&gt;contacted&lt;/code&gt;. No handler. No enrollment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bulk import
  → write lead to `contacted` state
                                      ← nothing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The downstream automation that sends follow-ups reads the &lt;code&gt;leads&lt;/code&gt; table and looks for enrolled leads in &lt;code&gt;contacted&lt;/code&gt; state. The imported leads were in the right table, in the right state, but they were never enrolled. The automation skipped them every single cycle — correctly, by its own logic.&lt;/p&gt;

&lt;p&gt;No error. No warning. Just silence.&lt;/p&gt;

&lt;p&gt;The gap never closed because there was no reconciliation job. Nothing periodically asked "are there &lt;code&gt;contacted&lt;/code&gt; leads that aren't in the sequence?" The imported population just sat there.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this is easy to miss
&lt;/h2&gt;

&lt;p&gt;The table looked fine. The leads looked fine. The automation looked fine.&lt;/p&gt;

&lt;p&gt;The bug was architectural: there were two write routes into one table, and only one of them ran the setup step the other parts of the system depended on. The system had no way to detect the discrepancy because it never compared "who should be enrolled" against "who is enrolled." It just processed whoever was already enrolled.&lt;/p&gt;

&lt;p&gt;This is what I'd call a dead population path. The rows exist. The automation is running. The rows are just invisible to it.&lt;/p&gt;

&lt;p&gt;Bulk imports create this constantly because they're designed to skip your application layer — that's literally their value proposition. The problem is your application layer is often where side effects live.&lt;/p&gt;




&lt;h2&gt;
  
  
  The fix: enforce enrollment at the database layer
&lt;/h2&gt;

&lt;p&gt;The only reliable fix is moving the enrollment check to a place that runs regardless of how the row was written. Postgres triggers do this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- The trigger function&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;enroll_contacted_lead&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
  &lt;span class="c1"&gt;-- Only act on rows entering or already in 'contacted' state&lt;/span&gt;
  &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'contacted'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
    &lt;span class="c1"&gt;-- Check whether this lead qualifies for enrollment&lt;/span&gt;
    &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assigned_to&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
      &lt;span class="c1"&gt;-- Enroll if not already enrolled&lt;/span&gt;
      &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;follow_up_enrollments&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lead_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;enrolled_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sequence_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sequence_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;CONFLICT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lead_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="k"&gt;NOTHING&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;ELSE&lt;/span&gt;
      &lt;span class="c1"&gt;-- Log why it was skipped so you can find these later&lt;/span&gt;
      &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;enrollment_skipped_log&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lead_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;skipped_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;CASE&lt;/span&gt;
          &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assigned_to&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'no_assignee'&lt;/span&gt;
          &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'no_email'&lt;/span&gt;
          &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;'unknown'&lt;/span&gt;
        &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;CONFLICT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lead_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="k"&gt;NOTHING&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Attach it to both INSERT and UPDATE&lt;/span&gt;
&lt;span class="c1"&gt;-- so it catches bulk imports (INSERT) and status changes (UPDATE) alike&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;trg_enroll_contacted_lead&lt;/span&gt;
&lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;OF&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;leads&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;span class="k"&gt;EXECUTE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;enroll_contacted_lead&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth noting about this implementation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;ON CONFLICT DO NOTHING&lt;/code&gt;&lt;/strong&gt; on the enrollment insert means existing correctly-enrolled leads are untouched. Safe to run on a table that already has good data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The skip log&lt;/strong&gt; is not optional. If a lead doesn't qualify, you want to know why. Without it, you're back to silent failures. The log gives you a query you can run to find every lead that got skipped and the reason.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;AFTER INSERT OR UPDATE OF status&lt;/code&gt;&lt;/strong&gt; covers both paths. The bulk import fires &lt;code&gt;INSERT&lt;/code&gt;. A status change through the application fires &lt;code&gt;UPDATE&lt;/code&gt;. Both paths now run the same enrollment logic.&lt;/p&gt;

&lt;p&gt;In Supabase, you apply this in the SQL editor or through a migration file. No special Supabase API needed — it's just Postgres.&lt;/p&gt;




&lt;h2&gt;
  
  
  Backfilling the existing gap
&lt;/h2&gt;

&lt;p&gt;The trigger handles new rows. But the affected leads already in the table needed a one-time backfill.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Enroll any existing contacted leads that slipped through&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;follow_up_enrollments&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lead_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;enrolled_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sequence_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sequence_id&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;leads&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;follow_up_enrollments&lt;/span&gt; &lt;span class="n"&gt;fe&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;fe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lead_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
  &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'contacted'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;fe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lead_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;           &lt;span class="c1"&gt;-- not already enrolled&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assigned_to&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;    &lt;span class="c1"&gt;-- qualifies&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="c1"&gt;-- qualifies&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this once after deploying the trigger. Then the trigger maintains the invariant going forward.&lt;/p&gt;




&lt;h2&gt;
  
  
  The question to ask about every table your automation reads
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;What are all the ways rows get into this table, and does each one run the steps downstream assumes happened?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For most tables the answer is one or two paths and they're all through your application code. Fine.&lt;/p&gt;

&lt;p&gt;But the moment you add a bulk import, a migration script, a database-level upsert from an external tool, or a direct admin write — you've added a path that bypasses your application layer. Any side effects that lived there are now missing for those rows.&lt;/p&gt;

&lt;p&gt;The fix isn't to never import. It's to stop trusting that write path determines setup steps. Push the invariants down to the database, where they apply to every write regardless of origin.&lt;/p&gt;




&lt;h2&gt;
  
  
  Honest lesson
&lt;/h2&gt;

&lt;p&gt;This was a silent failure that looked like working software. The table had data. The automation was running. The logs showed no errors. The only way to notice was to ask why two groups with the same status were having different outcomes — and actually go find the answer.&lt;/p&gt;

&lt;p&gt;AI systems like ARIA that run autonomously are more exposed to this class of bug, not less. There's no human checking each lead. The automation either processes them or it doesn't, and if it doesn't, you need something in the system that catches the gap. A trigger logging skipped records is a simple form of that. A reconciliation job that periodically diffs expected vs actual enrollment is a stronger one.&lt;/p&gt;

&lt;p&gt;Both beat finding out months later when a customer asks why no one followed up.&lt;/p&gt;




&lt;p&gt;— Mike Clarke, founder of Elevare Digital.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>coding</category>
      <category>webdev</category>
      <category>learning</category>
    </item>
    <item>
      <title>The table looked general-purpose. The schema disagreed.</title>
      <dc:creator>Mike Clarke</dc:creator>
      <pubDate>Tue, 21 Jul 2026 14:00:09 +0000</pubDate>
      <link>https://dev.to/mike_clarke_50a95013f5c59/the-table-looked-general-purpose-the-schema-disagreed-9h9</link>
      <guid>https://dev.to/mike_clarke_50a95013f5c59/the-table-looked-general-purpose-the-schema-disagreed-9h9</guid>
      <description>&lt;p&gt;A CHECK constraint is documentation with teeth.&lt;/p&gt;

&lt;p&gt;That's the whole lesson. But here's what it looks like when you learn it at runtime instead of at read-time.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Key Takeaways&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A CHECK constraint encodes scope assumptions the column name never will&lt;/li&gt;
&lt;li&gt;A constraint violation inside a trigger aborts the parent transaction — silently, from the caller's perspective&lt;/li&gt;
&lt;li&gt;Before writing to a table you inherited, run &lt;code&gt;\d tablename&lt;/code&gt; or query &lt;code&gt;information_schema.check_constraints&lt;/code&gt;. Read what's there.&lt;/li&gt;
&lt;li&gt;Gate on allowed values in code before the insert. Let the constraint be a backstop, not the first line of defense.&lt;/li&gt;
&lt;li&gt;Skip-and-log beats throw-and-abort when the parent write is more important than the child record&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What happened
&lt;/h2&gt;

&lt;p&gt;We run ARIA, an autonomous CRM and nurture automation system at Elevare Digital. New contact records enroll into follow-up sequences automatically — no human queues the work.&lt;/p&gt;

&lt;p&gt;At some point, new records stopped enrolling. The parent write (creating the contact) was aborting entirely. No sequence. No contact. No error surfaced to the caller in a useful way.&lt;/p&gt;

&lt;p&gt;The culprit was a &lt;code&gt;region&lt;/code&gt; column with a CHECK constraint that looked roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;enrollment_tracker&lt;/span&gt;
  &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;region_allowed&lt;/span&gt;
  &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'north'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'south'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'east'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'west'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'central'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The table had been built for one specific program covering five regions. Later, code started treating it as a general enrollment tracker and writing region values the constraint never anticipated. Postgres rejected the insert. Because that insert happened inside a trigger, the whole parent transaction rolled back.&lt;/p&gt;

&lt;p&gt;The column was named &lt;code&gt;region&lt;/code&gt;. Nothing in that name says "only these five values are valid." The constraint said it. Nobody read the constraint.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why a trigger makes this worse
&lt;/h2&gt;

&lt;p&gt;If you insert directly into a table and violate a CHECK, you get an error back immediately. Annoying, but contained.&lt;/p&gt;

&lt;p&gt;When the insert is inside a trigger on a &lt;em&gt;different&lt;/em&gt; table, the error propagates up and aborts the statement that fired the trigger. The caller sees their write fail. They may have no idea a trigger was involved, let alone which constraint fired inside it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Trigger fires on INSERT to contacts&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;enroll_contact&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;trigger&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
  &lt;span class="c1"&gt;-- This insert can blow up the parent INSERT INTO contacts&lt;/span&gt;
  &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;enrollment_tracker&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contact_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;enrolled_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

  &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;trg_enroll&lt;/span&gt;
&lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;contacts&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt; &lt;span class="k"&gt;EXECUTE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;enroll_contact&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;contacts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s1"&gt;'Acme Corp'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'southeast'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- ERROR: new row for relation "enrollment_tracker" violates&lt;/span&gt;
&lt;span class="c1"&gt;-- check constraint "region_allowed"&lt;/span&gt;
&lt;span class="c1"&gt;-- DETAIL: Failing row contains (..., southeast, ...).&lt;/span&gt;
&lt;span class="c1"&gt;-- The contact was NOT created.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;southeast&lt;/code&gt; is a perfectly valid business concept. The table just never knew about it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The fix: gate before you insert
&lt;/h2&gt;

&lt;p&gt;Once we understood the constraint, the fix was straightforward. Check the allowed values in code (or in the trigger function itself) before attempting the insert. If the value isn't allowed, skip the enrollment record and log it. The parent write completes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;enroll_contact&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;trigger&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt;
  &lt;span class="n"&gt;allowed_regions&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ARRAY&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'north'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'south'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'east'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'west'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'central'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
  &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;ANY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allowed_regions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
    &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;enrollment_tracker&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contact_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;enrolled_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="k"&gt;ELSE&lt;/span&gt;
    &lt;span class="c1"&gt;-- Log it; don't abort the parent write&lt;/span&gt;
    &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;enrollment_skipped&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contact_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;skipped_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s1"&gt;'region not in enrollment_tracker allowed list'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternately, you can query the constraint definition directly rather than hardcoding the list — which is useful if the allowed values might expand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Pull allowed values from the constraint definition at runtime&lt;/span&gt;
&lt;span class="c1"&gt;-- (useful for visibility; hardcoding is fine if values are stable)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;consrc&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_constraint&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;conname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'region_allowed'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;conrelid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'enrollment_tracker'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;regclass&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returns something like &lt;code&gt;(region = ANY (ARRAY['north'::text, 'south'::text, ...]))&lt;/code&gt;. Not the cleanest parse, but it tells you exactly what the schema intended.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to read the constraints you inherited
&lt;/h2&gt;

&lt;p&gt;Before writing to any table you didn't build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- psql shortcut&lt;/span&gt;
&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="n"&gt;enrollment_tracker&lt;/span&gt;

&lt;span class="c1"&gt;-- Or query directly&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;constraint_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;constraint_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;cc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;check_clause&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;table_constraints&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;check_constraints&lt;/span&gt; &lt;span class="n"&gt;cc&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;constraint_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;constraint_name&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;table_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'enrollment_tracker'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Constraints you'll find this way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CHECK&lt;/code&gt; — allowed values, ranges, cross-column rules&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UNIQUE&lt;/code&gt; — uniqueness you may not have assumed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NOT NULL&lt;/code&gt; — columns the schema considers required&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FOREIGN KEY&lt;/code&gt; — referential dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is hidden. It's just rarely read.&lt;/p&gt;




&lt;h2&gt;
  
  
  The actual lesson
&lt;/h2&gt;

&lt;p&gt;The table name was &lt;code&gt;enrollment_tracker&lt;/code&gt;. That sounds general. It wasn't — it was built for a specific program with five regions, and the CHECK constraint was the only place that scope was written down.&lt;/p&gt;

&lt;p&gt;When later code treated it as a general tracker, it imported an assumption it never knew was there. The schema surfaced that assumption at write time, inside a trigger, in a way that took down the parent record.&lt;/p&gt;

&lt;p&gt;Schema constraints are the closest thing to binding documentation that most databases have. They don't drift. They don't get outdated and left in a wiki. They're enforced.&lt;/p&gt;

&lt;p&gt;Read them before you write. Not after.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;— Mike Clarke, founder of Elevare Digital.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>coding</category>
      <category>webdev</category>
      <category>learning</category>
    </item>
    <item>
      <title>Your AI agent checked its queue, found nothing, and went back to sleep. The queue was full.</title>
      <dc:creator>Mike Clarke</dc:creator>
      <pubDate>Fri, 17 Jul 2026 14:00:09 +0000</pubDate>
      <link>https://dev.to/mike_clarke_50a95013f5c59/your-ai-agent-checked-its-queue-found-nothing-and-went-back-to-sleep-the-queue-was-full-1m4</link>
      <guid>https://dev.to/mike_clarke_50a95013f5c59/your-ai-agent-checked-its-queue-found-nothing-and-went-back-to-sleep-the-queue-was-full-1m4</guid>
      <description>&lt;p&gt;Postgres Row-Level Security doesn't raise an error when it blocks you. It returns zero rows. Your query succeeds. Your agent sees an empty queue. Your agent idles. Your jobs pile up.&lt;/p&gt;

&lt;p&gt;This is what happened to ARIA, our autonomous AI system at Elevare Digital.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Postgres RLS silently filters rows on &lt;code&gt;SELECT&lt;/code&gt; — a blocked read and a genuinely empty table look identical to the caller&lt;/li&gt;
&lt;li&gt;An orchestrator that reads its own queue gets no exception, no warning, no non-200 status code when RLS blocks it&lt;/li&gt;
&lt;li&gt;A healthy heartbeat on an idle agent tells you nothing about whether the idle is real&lt;/li&gt;
&lt;li&gt;The fix: add a canary count that verifies you &lt;em&gt;can&lt;/em&gt; read, not just that you &lt;em&gt;did&lt;/em&gt; read&lt;/li&gt;
&lt;li&gt;Treat an empty read as a question, not an answer&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What the agent saw
&lt;/h2&gt;

&lt;p&gt;The orchestrator polls a work queue table. Normal operation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Query for pending jobs&lt;/li&gt;
&lt;li&gt;If results → process them&lt;/li&gt;
&lt;li&gt;If empty → log idle heartbeat, sleep&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The logs showed healthy idle heartbeats. Monitoring showed the agent alive and polling. From the outside, everything looked fine.&lt;/p&gt;

&lt;p&gt;Jobs were not being processed.&lt;/p&gt;




&lt;h2&gt;
  
  
  What actually happened
&lt;/h2&gt;

&lt;p&gt;Someone added a Row-Level Security policy to the queue table, scoped to &lt;code&gt;auth.uid()&lt;/code&gt;. Correct for user-facing reads. But the orchestrator connects under the service role — and no service-role bypass was added to the policy.&lt;/p&gt;

&lt;p&gt;Here's the policy as it was written:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Added for user-facing queue visibility&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;POLICY&lt;/span&gt; &lt;span class="nv"&gt;"users_see_own_jobs"&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;work_queue&lt;/span&gt;
  &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And RLS was enabled on the table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;work_queue&lt;/span&gt; &lt;span class="n"&gt;ENABLE&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt; &lt;span class="k"&gt;LEVEL&lt;/span&gt; &lt;span class="k"&gt;SECURITY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No bypass for the service role. No &lt;code&gt;FOR ALL&lt;/code&gt; escape. The orchestrator's &lt;code&gt;SELECT&lt;/code&gt; now matched zero rows — because RLS filtered every row out before returning results.&lt;/p&gt;

&lt;p&gt;Postgres does not raise. It does not warn. The query completes with status 200 and an empty array.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Orchestrator poll — looks completely normal&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;work_queue&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="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;*&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="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status&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;pending&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// error is null. jobs is []. Agent concludes: nothing to do.&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;jobs&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;recordIdleHeartbeat&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;error&lt;/code&gt; is null. The &lt;code&gt;jobs&lt;/code&gt; array is empty. The agent's logic is correct for the data it received. The data it received was wrong in a way that produced no signal.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this is hard to catch
&lt;/h2&gt;

&lt;p&gt;If RLS blocks a write, you often get an error — the row you tried to insert violates a policy, or returns nothing when you expected a rowcount. Writes are easier to notice.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SELECT&lt;/code&gt; under RLS is different. The design intent is that users should not be able to distinguish "this row doesn't exist" from "this row exists but you can't see it." That's a security feature. It's also exactly the wrong behavior for an orchestrator that needs to know the difference between "queue is empty" and "I am blind."&lt;/p&gt;

&lt;p&gt;The service role in Supabase bypasses RLS by default — but only if you're using the service-role key on the client. If your edge function or backend is using the anon key, or if it's operating in a context where &lt;code&gt;auth.uid()&lt;/code&gt; resolves to null, RLS applies and silently filters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- This is what the orchestrator needed, but wasn't there&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;POLICY&lt;/span&gt; &lt;span class="nv"&gt;"service_role_bypass"&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;work_queue&lt;/span&gt;
  &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;
  &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;role&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'service_role'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Or, simpler: grant the service role explicit bypass at the table level&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;work_queue&lt;/span&gt; &lt;span class="k"&gt;FORCE&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt; &lt;span class="k"&gt;LEVEL&lt;/span&gt; &lt;span class="k"&gt;SECURITY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;-- applies to all&lt;/span&gt;
&lt;span class="c1"&gt;-- and then in your Supabase client: use the service-role key, which bypasses RLS automatically&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual bypass mechanism in Supabase is that the service-role JWT includes &lt;code&gt;"role": "service_role"&lt;/code&gt;, and Supabase's Postgres config grants that role &lt;code&gt;BYPASSRLS&lt;/code&gt;. If your client is initialized with the service-role key, you're fine. If it's not, RLS applies — no error, just silence.&lt;/p&gt;




&lt;h2&gt;
  
  
  The fix: a canary count
&lt;/h2&gt;

&lt;p&gt;The real repair has two parts: fix the RLS policy, and add a check that will catch this failure mode again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 1 — Fix the policy:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Preserve user-facing policy&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;POLICY&lt;/span&gt; &lt;span class="nv"&gt;"users_see_own_jobs"&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;work_queue&lt;/span&gt;
  &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Add explicit service-role access&lt;/span&gt;
&lt;span class="c1"&gt;-- (Or: initialize your orchestrator client with the service-role key,&lt;/span&gt;
&lt;span class="c1"&gt;--  which bypasses RLS automatically via BYPASSRLS grant)&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;POLICY&lt;/span&gt; &lt;span class="nv"&gt;"orchestrator_full_access"&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;work_queue&lt;/span&gt;
  &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;
  &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;role&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'service_role'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;role&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'service_role'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Part 2 — The canary check:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The orchestrator now runs a canary query before trusting an empty result. The canary queries a row it knows exists — a sentinel row inserted specifically for this purpose, or a count from an unrestricted table the orchestrator owns.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;pollQueue&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;queueError&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;work_queue&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="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;*&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="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status&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;pending&lt;/span&gt;&lt;span class="dl"&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;queueError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;alertOpsChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;queue_poll_error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;queueError&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="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Jobs came back — process normally&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;jobs&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;processJobs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jobs&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="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Empty result — but is it genuinely empty, or are we blind?&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canaryOk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;verifyReadAccess&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;canaryOk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// This is the case we were missing before&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;alertOpsChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;queue_read_access_lost&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Orchestrator received empty queue result but canary check failed. Possible RLS or permission regression.&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="c1"&gt;// Do NOT record idle heartbeat — we don't know the real state&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Canary passed — empty really means empty&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;recordIdleHeartbeat&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;verifyReadAccess&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Query a sentinel row that always exists in a known state&lt;/span&gt;
  &lt;span class="c1"&gt;// This could be a dedicated canary table, or a system-health row&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;orchestrator_canary&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="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;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="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&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;heartbeat-sentinel&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="nf"&gt;single&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;error&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// We expected exactly one row. Getting nothing means access is broken.&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The canary table is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;orchestrator_canary&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;orchestrator_canary&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'heartbeat-sentinel'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- No RLS on this table — it exists only so the orchestrator can verify it can read&lt;/span&gt;
&lt;span class="c1"&gt;-- If you want RLS: add only a service-role policy, nothing user-facing&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the orchestrator has three states instead of two:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Jobs found → process&lt;/li&gt;
&lt;li&gt;No jobs, canary ok → genuinely idle&lt;/li&gt;
&lt;li&gt;No jobs, canary failed → alert, do not idle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The third state was always real. We just had no way to observe it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The deeper issue with autonomous agents and silent failures
&lt;/h2&gt;

&lt;p&gt;Human-in-the-loop systems fail loudly. A user tries to load their queue, sees nothing, and files a ticket. An autonomous agent has no user. It reads, decides, acts. If the read is silently wrong, the decision is wrong, and nothing complains.&lt;/p&gt;

&lt;p&gt;This failure mode matters more as systems get more autonomous. ARIA processes queued work without someone watching every poll cycle. The assumption baked into the polling loop was: &lt;em&gt;an empty read means there is nothing to do.&lt;/em&gt; That assumption held until it didn't, and the system had no way to question it.&lt;/p&gt;

&lt;p&gt;The fix is to make the orchestrator skeptical of its own empty results. Not paranoid — just skeptical enough to verify the precondition that makes "empty" meaningful.&lt;/p&gt;




&lt;h2&gt;
  
  
  What to check in your own system
&lt;/h2&gt;

&lt;p&gt;If you're running any kind of queue worker against Postgres or Supabase:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Check which key your worker is using.&lt;/strong&gt; Supabase service-role key bypasses RLS. Anon key does not. Confirm which one is in your edge function or backend environment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;List your RLS policies and check for missing bypasses.&lt;/strong&gt; &lt;code&gt;SELECT * FROM pg_policies WHERE tablename = 'your_queue_table';&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Look at your idle heartbeats.&lt;/strong&gt; If idle logging increased around the time you last modified permissions or added RLS, that's worth investigating.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add a canary.&lt;/strong&gt; Takes an hour. Catches this entire class of problem permanently.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;An empty queue result is not evidence of an empty queue. It's evidence that the query ran. Those are different things, and in autonomous systems, the difference matters.&lt;/p&gt;

&lt;p&gt;— Mike Clarke, founder of Elevare Digital.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>coding</category>
      <category>webdev</category>
      <category>learning</category>
    </item>
    <item>
      <title>The function was deployed, healthy, and never ran once</title>
      <dc:creator>Mike Clarke</dc:creator>
      <pubDate>Tue, 14 Jul 2026 14:00:05 +0000</pubDate>
      <link>https://dev.to/mike_clarke_50a95013f5c59/the-function-was-deployed-healthy-and-never-ran-once-1hgd</link>
      <guid>https://dev.to/mike_clarke_50a95013f5c59/the-function-was-deployed-healthy-and-never-ran-once-1hgd</guid>
      <description>&lt;p&gt;There's a category of bug where nothing is broken. The dashboard is green. The deployment succeeded. The function shows a recent update timestamp. And it has done zero work since you shipped it.&lt;/p&gt;

&lt;p&gt;We hit this in ARIA, our autonomous agent system. A scheduled function was silently doing nothing — not erroring, not timing out, just... absent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Takeaways&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Supabase edge function can be perfectly deployed and still never execute&lt;/li&gt;
&lt;li&gt;JWT verification happens at the gateway, before your handler runs&lt;/li&gt;
&lt;li&gt;Gateway-level 401s produce no application logs, no error rows, no telemetry inside the function&lt;/li&gt;
&lt;li&gt;The only signal is the absence of invocation-log entries&lt;/li&gt;
&lt;li&gt;Functions called by &lt;code&gt;pg_cron&lt;/code&gt; need &lt;code&gt;verify_jwt=false&lt;/code&gt; if they authenticate via service-role key in the body&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What we saw
&lt;/h2&gt;

&lt;p&gt;ARIA runs a multi-agent scheduler. Several edge functions are invoked on a schedule via &lt;code&gt;pg_cron&lt;/code&gt;. One of them showed up in the Supabase dashboard as deployed, healthy, recently updated. We had no alerts firing.&lt;/p&gt;

&lt;p&gt;What we didn't have: any invocation log rows. Not failed ones. Not slow ones. Zero rows.&lt;/p&gt;

&lt;p&gt;Everything looked fine. Nothing was running.&lt;/p&gt;




&lt;h2&gt;
  
  
  The mechanism
&lt;/h2&gt;

&lt;p&gt;Supabase edge functions have a &lt;code&gt;verify_jwt&lt;/code&gt; flag. When it's set to &lt;code&gt;true&lt;/code&gt; (the default), the gateway validates the bearer token on every request before the function body executes.&lt;/p&gt;

&lt;p&gt;Our &lt;code&gt;pg_cron&lt;/code&gt; job was calling the function with a service-role bearer token — correct credentials, correct format. But the gateway's JWT verification was rejecting it and returning a &lt;code&gt;401&lt;/code&gt; before the handler ever ran.&lt;/p&gt;

&lt;p&gt;The request never reached the function. The function had nothing to log.&lt;/p&gt;

&lt;p&gt;Here's what the pg_cron invocation looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s1"&gt;'run-agent-task'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'* * * * *'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="err"&gt;$$&lt;/span&gt;
  &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;http_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://&amp;lt;project&amp;gt;.supabase.co/functions/v1/agent-task'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsonb_build_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s1"&gt;'Content-Type'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s1"&gt;'Authorization'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Bearer '&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;current_setting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'app.service_role_key'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'{}'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;jsonb&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here's the function config that caused the silent failure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# supabase/functions/agent-task/config.toml&lt;/span&gt;
&lt;span class="nn"&gt;[functions.agent-task]&lt;/span&gt;
&lt;span class="py"&gt;verify_jwt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;  &lt;span class="c"&gt;# &amp;lt;-- gateway rejects the cron call here, handler never runs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix is one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[functions.agent-task]&lt;/span&gt;
&lt;span class="py"&gt;verify_jwt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;  &lt;span class="c"&gt;# gateway passes the request through; function handles auth itself&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;verify_jwt=false&lt;/code&gt;, you move the authentication check inside the function body, where you can actually see what happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;serve&lt;/span&gt; &lt;span class="p"&gt;}&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;https://deno.land/std@0.168.0/http/server.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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;authHeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&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;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;authHeader&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bearer &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="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Validate against your service role key inside the handler&lt;/span&gt;
  &lt;span class="c1"&gt;// where failures are visible in your logs&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;token&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SUPABASE_SERVICE_ROLE_KEY&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;Unauthorized request to agent-task&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unauthorized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// actual work happens here&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;agent-task executing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&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;Now when auth fails, you get a log line. You get an invocation row. You get something to look at.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this is hard to catch
&lt;/h2&gt;

&lt;p&gt;The normal debugging loop assumes that if something failed, there's a record of the failure. You look at logs, you find the error, you fix it.&lt;/p&gt;

&lt;p&gt;Gateway-level rejections break that loop. The 401 happens in infrastructure you don't control and don't have direct log access to. From the function's perspective, the request never arrived. From the cron job's perspective, it fired successfully — &lt;code&gt;net.http_post&lt;/code&gt; queued the request and moved on.&lt;/p&gt;

&lt;p&gt;The only diagnostic signal is the shape of what's missing: no invocation rows in the function's log table, for a function that should be running every minute.&lt;/p&gt;

&lt;p&gt;If you're not actively checking for expected invocations, you won't notice.&lt;/p&gt;




&lt;h2&gt;
  
  
  What we added after
&lt;/h2&gt;

&lt;p&gt;Beyond fixing the flag, we added a simple liveness check: a separate monitor queries the invocation log and alerts if a scheduled function hasn't produced a row in longer than two of its scheduled intervals. Not sophisticated, but it catches the "deployed and doing nothing" class of failure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Example: check that agent-task has run in the last 3 minutes&lt;/span&gt;
&lt;span class="c1"&gt;-- (for a function scheduled every minute)&lt;/span&gt;
&lt;span class="k"&gt;select&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="k"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'3 minutes'&lt;/span&gt;
    &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="s1"&gt;'STALLED'&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s1"&gt;'OK'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;function_invocation_log&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;function_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'agent-task'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The specific table structure depends on how you surface invocation logs in your setup — the point is to query for expected evidence of execution, not just absence of errors.&lt;/p&gt;




&lt;h2&gt;
  
  
  The thing worth internalizing
&lt;/h2&gt;

&lt;p&gt;Deployment success and runtime success are different things. A function can pass every health check and still never execute a single line of your code.&lt;/p&gt;

&lt;p&gt;For any function invoked by infrastructure (cron, queues, webhooks) rather than by a user, you need positive confirmation of execution — not just the absence of errors. Absence of errors and absence of execution look identical from the outside.&lt;/p&gt;

&lt;p&gt;Check your cron-invoked functions. If &lt;code&gt;verify_jwt=true&lt;/code&gt; and you're calling with a service-role token, check the invocation log. The rows might not be there.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;— Mike Clarke, founder of Elevare Digital.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>coding</category>
      <category>webdev</category>
      <category>learning</category>
    </item>
    <item>
      <title>Your cron logged success every day for weeks. It approved nothing.</title>
      <dc:creator>Mike Clarke</dc:creator>
      <pubDate>Sun, 12 Jul 2026 00:10:11 +0000</pubDate>
      <link>https://dev.to/mike_clarke_50a95013f5c59/your-cron-logged-success-every-day-for-weeks-it-approved-nothing-1llg</link>
      <guid>https://dev.to/mike_clarke_50a95013f5c59/your-cron-logged-success-every-day-for-weeks-it-approved-nothing-1llg</guid>
      <description>&lt;p&gt;Your cron logged success every day for weeks. It approved nothing.&lt;/p&gt;




&lt;p&gt;We run ARIA, an autonomous content pipeline at Elevare Digital. Agents generate drafts, a daily approver cron reviews them, approved content moves to publishing. Fully automated, gated by a human-review layer, self-healing in most places.&lt;/p&gt;

&lt;p&gt;For several weeks, the approver cron ran on schedule, logged &lt;code&gt;{ status: 'success' }&lt;/code&gt;, and approved zero drafts. Nineteen drafts sat in the queue. Nobody was alerted. The system looked healthy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Takeaways&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A cron that processes zero rows and a cron that processes zero rows &lt;em&gt;because there's nothing to process&lt;/em&gt; produce identical logs.&lt;/li&gt;
&lt;li&gt;PostgREST inner joins silently exclude rows when the join condition matches nothing — no error, no warning, just an empty result set.&lt;/li&gt;
&lt;li&gt;Producer/consumer type drift is invisible until you instrument the gap between "rows waiting" and "rows processed."&lt;/li&gt;
&lt;li&gt;Alert on &lt;code&gt;pending &amp;gt; 0 AND processed === 0&lt;/code&gt;, not just on thrown errors.&lt;/li&gt;
&lt;li&gt;Autonomous systems fail most dangerously in the space between errors and correctness.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What the system was doing
&lt;/h2&gt;

&lt;p&gt;ARIA's generator agents write content and store drafts as rows in a &lt;code&gt;content_opportunities&lt;/code&gt; table. Each row has an &lt;code&gt;opportunity_type&lt;/code&gt; — in practice, the generator was producing &lt;code&gt;'article'&lt;/code&gt; and &lt;code&gt;'listing'&lt;/code&gt; types.&lt;/p&gt;

&lt;p&gt;The approver cron queries that table via Supabase / PostgREST and processes any pending drafts it finds. Here's the shape of what it was doing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// approver-cron/index.ts (Deno edge function)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;opportunities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content_opportunities&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="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
    id,
    title,
    status,
    content_threads!inner(
      id,
      body
    )
  `&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status&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;pending_review&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="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;opportunity_type&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;thread&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- the problem&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;error&lt;/span&gt;&lt;span class="p"&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;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;Approver query failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="p"&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Approver run complete. Processed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;opportunities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// ... process opportunities&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things combined to create the silent failure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;opportunity_type = 'thread'&lt;/code&gt; — the generator never wrote rows with this type. It wrote &lt;code&gt;'article'&lt;/code&gt; and &lt;code&gt;'listing'&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;content_threads!inner(...)&lt;/code&gt; — PostgREST inner join syntax. If no matching rows exist in &lt;code&gt;content_threads&lt;/code&gt;, the parent rows are excluded entirely.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result: &lt;code&gt;opportunities&lt;/code&gt; was always an empty array. No error was thrown. &lt;code&gt;opportunities.length&lt;/code&gt; logged as &lt;code&gt;0&lt;/code&gt;. The function returned &lt;code&gt;200 OK&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is the worst kind of failure
&lt;/h2&gt;

&lt;p&gt;Most failures announce themselves. A network timeout throws. A missing env var crashes on startup. A malformed query returns a PostgREST error object.&lt;/p&gt;

&lt;p&gt;This failure looked like rest.&lt;/p&gt;

&lt;p&gt;In a queue system, there's a legitimate state where the consumer runs and finds nothing to do — because the queue is empty. That's healthy-idle. It logs &lt;code&gt;Processed: 0&lt;/code&gt; and exits cleanly.&lt;/p&gt;

&lt;p&gt;Our broken state also logged &lt;code&gt;Processed: 0&lt;/code&gt; and exited cleanly.&lt;/p&gt;

&lt;p&gt;From the outside, from a log aggregator, from a dashboard: identical.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Healthy idle — queue is empty
Approver run complete. Processed: 0

// Broken — 19 drafts waiting, filter excludes all of them  
Approver run complete. Processed: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only way to tell them apart is to check whether the queue &lt;em&gt;actually had pending rows&lt;/em&gt; when the consumer ran.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: broaden scope, instrument the gap
&lt;/h2&gt;

&lt;p&gt;The immediate fix was to remove the incorrect type filter and fix the join. The approver should consume all pending drafts, not just ones of a type the generator never produces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Fixed: consume what the producer actually writes&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;opportunities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content_opportunities&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="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
    id,
    title,
    status,
    opportunity_type
  `&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status&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;pending_review&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// No type filter. No inner join restricting to non-existent rows.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the more important fix was the alert. After each consumer run, we now check whether there are pending rows that weren't touched:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// After the approver processes its batch:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;processed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;opportunities&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stillPending&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content_opportunities&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="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;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="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;head&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status&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;pending_review&lt;/span&gt;&lt;span class="dl"&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;processed&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stillPending&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Consumer ran, touched nothing, but the queue has rows.&lt;/span&gt;
  &lt;span class="c1"&gt;// This is the danger zone.&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;triggerAlert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;consumer_drift&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Approver processed 0 rows but queue has &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;stillPending&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; pending drafts.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Processed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;processed&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Still pending: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;stillPending&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This check doesn't care &lt;em&gt;why&lt;/em&gt; the consumer touched zero rows. It cares that the queue had work and the consumer didn't do it. The reason might be a bad filter, a broken join, a type mismatch, or something else we haven't thought of yet. The alert fires regardless and forces a human to look.&lt;/p&gt;

&lt;h2&gt;
  
  
  The underlying pattern: producer/consumer drift
&lt;/h2&gt;

&lt;p&gt;The generator and approver were written at different times. At some point, the generator's output types and the approver's filter drifted apart. Neither system knew about the other's contract. There was no shared schema validation between them, no test that asserted "the approver can actually see what the generator writes."&lt;/p&gt;

&lt;p&gt;This is a normal way systems decay. The producer evolves. The consumer doesn't. Or vice versa. In a synchronous API call, this kind of mismatch usually causes a visible failure. In a queue-based async pipeline, it just causes the queue to grow while the consumer reports success.&lt;/p&gt;

&lt;p&gt;Autonomous systems make this worse because there's no human in the loop watching the queue depth during normal operation. The whole point is that it runs without supervision. Which means the instrumentation &lt;em&gt;is&lt;/em&gt; the supervision.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we'd do differently
&lt;/h2&gt;

&lt;p&gt;A few things that would have caught this earlier:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Log scanned vs. processed separately.&lt;/strong&gt; If your consumer query returns zero rows, log that distinctly from "query returned rows and we processed them." &lt;code&gt;scanned: 0, processed: 0&lt;/code&gt; vs &lt;code&gt;scanned: 5, processed: 5&lt;/code&gt; are different states.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Track queue depth over time.&lt;/strong&gt; If pending rows are accumulating across multiple cron runs, that's a signal even without a consumer error. A simple check: if the queue depth at the end of a run is higher than at the start, something isn't working.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test the consumer against real producer output.&lt;/strong&gt; Not just "does the function run without throwing" but "given a row the producer actually writes, does the consumer find and process it."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Name your contracts.&lt;/strong&gt; If the producer writes &lt;code&gt;opportunity_type: 'article'&lt;/code&gt; and the consumer filters on &lt;code&gt;opportunity_type: 'thread'&lt;/code&gt;, that's a broken contract. Treat it like one — define it explicitly, validate it, test it.&lt;/p&gt;




&lt;p&gt;The honest summary: a filter we forgot about, combined with an inner join that excluded everything, turned our approver cron into a machine that ran on schedule, logged success, and accomplished nothing for weeks. The fix was straightforward. The lesson is that in autonomous pipelines, success-on-empty is a failure mode you have to design against explicitly, because the logs will never tell you on their own.&lt;/p&gt;

&lt;p&gt;Alert on the gap between what the queue holds and what the consumer touches. Not just on errors.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;— Mike Clarke, founder of Elevare Digital.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>coding</category>
      <category>webdev</category>
      <category>learning</category>
    </item>
    <item>
      <title>Smart Traffic Systems: Understanding and Configuring the Core Logic</title>
      <dc:creator>Mike Clarke</dc:creator>
      <pubDate>Wed, 01 Jul 2026 06:00:19 +0000</pubDate>
      <link>https://dev.to/mike_clarke_50a95013f5c59/smart-traffic-systems-understanding-and-configuring-the-core-logic-8hp</link>
      <guid>https://dev.to/mike_clarke_50a95013f5c59/smart-traffic-systems-understanding-and-configuring-the-core-logic-8hp</guid>
      <description>&lt;h3&gt;
  
  
  Stuck in Traffic? Let's Build a Smarter Way Forward.
&lt;/h3&gt;

&lt;p&gt;We've all been there: staring at a red light, no cross-traffic in sight, and wondering &lt;em&gt;why&lt;/em&gt; it's taking so long. Traditional, fixed-time traffic light systems are notoriously inefficient. They don't adapt to real-world conditions. But what if we could make them smarter? What if our traffic infrastructure could &lt;em&gt;think&lt;/em&gt;? &lt;/p&gt;

&lt;p&gt;That's where smart traffic systems come in. As developers, we have the power to build these adaptive, intelligent solutions that promise smoother commutes, reduced congestion, and lower emissions. But how do you &lt;em&gt;configure&lt;/em&gt; one?&lt;/p&gt;

&lt;h3&gt;
  
  
  The Brains Behind the Green Light: What Drives a Smart Traffic System?
&lt;/h3&gt;

&lt;p&gt;At its heart, a smart traffic system is a dynamic control loop. It observes, analyzes, decides, and then acts. Here's a breakdown of the key components you'll be configuring:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Data Ingestion:&lt;/strong&gt; This is your system's eyes and ears. We're talking about real-time input from diverse sources: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Vehicle Sensors:&lt;/strong&gt; Inductive loops, radar, lidar, cameras – detecting vehicle presence, speed, and count.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Pedestrian Sensors:&lt;/strong&gt; Infrared, pressure plates, computer vision for safe pedestrian crossings.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Environmental Data:&lt;/strong&gt; Time of day, weather conditions, historical traffic patterns.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Traffic Flow Analysis &amp;amp; Prediction:&lt;/strong&gt; Once you have the data, you need to make sense of it. This involves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Queue Length Estimation:&lt;/strong&gt; How many cars are waiting at each intersection approach?&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Travel Time Prediction:&lt;/strong&gt; How long will it take to clear the intersection?&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Pattern Recognition:&lt;/strong&gt; Identifying recurring congestion points or anomalies.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Decision-Making Engine (The Optimization Algorithm):&lt;/strong&gt; This is the core intelligence. Based on the analyzed data, the algorithm determines the optimal light phasing and timing. Common approaches include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Reinforcement Learning:&lt;/strong&gt; The system learns optimal strategies through trial and error over time.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Fuzzy Logic:&lt;/strong&gt; Handling imprecise or uncertain data (e.g., 'heavy traffic').&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Heuristic Algorithms:&lt;/strong&gt; Rule-based systems designed to achieve specific goals (e.g., minimize wait time, maximize throughput).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Actuation:&lt;/strong&gt; Finally, the system sends commands to the traffic light controllers to change their state (red, yellow, green) and duration.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  A Glimpse Under the Hood: Pseudocode for a Basic Adaptive System
&lt;/h3&gt;

&lt;p&gt;Let's consider a simplified scenario: a single intersection with four approaches. We want to minimize average wait time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FUNCTION ConfigureSmartTrafficSystem(intersectionID):
    // 1. Initialize Sensors &amp;amp; Data Streams
    sensors = GetSensorDataStreams(intersectionID)
    historical_data = LoadHistoricalTrafficPatterns(intersectionID)

    // 2. Define Optimization Goals (e.g., minimize average wait time, maximize throughput)
    optimization_goal = MINIMIZE_AVG_WAIT_TIME
    constraints = { MIN_GREEN_TIME: 10_seconds, MAX_GREEN_TIME: 60_seconds, YELLOW_TIME: 3_seconds }

    // 3. Main Control Loop
    LOOP indefinitely:
        current_vehicle_counts = ReadSensorData(sensors)
        current_pedestrian_counts = ReadPedestrianSensorData(sensors)
        current_time_of_day = GetCurrentTime()

        // 4. Analyze Traffic State
        queue_lengths = CalculateQueueLengths(current_vehicle_counts)
        demand_per_approach = EstimateDemand(current_vehicle_counts, historical_data, current_time_of_day)

        // 5. Decision-Making (using a simplified heuristic)
        next_phase_durations = DetermineOptimalPhaseDurations(
            queue_lengths,
            demand_per_approach,
            optimization_goal,
            constraints
        )

        // Example heuristic: Prioritize approach with most vehicles, but ensure fairness
        FUNCTION DetermineOptimalPhaseDurations(queues, demand, goal, constraints):
            priorities = CalculatePriorities(queues, demand) // e.g., higher queue means higher priority
            selected_phase = SelectPhaseBasedOnPriorities(priorities) 

            // Calculate green time for selected phase
            green_time = CalculateDynamicGreenTime(queues[selected_phase], constraints.MIN_GREEN_TIME, constraints.MAX_GREEN_TIME)

            RETURN { selected_phase: green_time, other_phases: constraints.YELLOW_TIME }

        // 6. Actuate Traffic Lights
        ApplyTrafficLightPhasing(intersectionID, next_phase_durations)

        WAIT(sum(next_phase_durations)) // Wait for the current cycle to complete
END FUNCTION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configuring this system involves fine-tuning your sensor interpretation, refining your prediction models, and, critically, selecting and optimizing your decision-making algorithm. Are you prioritizing throughput, fairness, emergency vehicle preemption, or perhaps a blend? Each choice impacts the system's behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Practice Matters: Beyond the Pseudocode
&lt;/h3&gt;

&lt;p&gt;Understanding the concepts is one thing; making them work in a complex, dynamic environment is another. Real-world traffic data is noisy, sensors can fail, and unexpected events (accidents, sudden surges in traffic) will constantly challenge your system. You'll need to consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Robustness:&lt;/strong&gt; How does your system handle missing or erroneous data?&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Scalability:&lt;/strong&gt; Can it manage hundreds or thousands of intersections?&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Latency:&lt;/strong&gt; Can it react quickly enough to changing conditions?&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Ethical Considerations:&lt;/strong&gt; Are your algorithms fair across different districts or demographic areas?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't just theory; it's about building resilient, impactful software. The best way to grasp these complexities is by doing. Simulating different scenarios, tweaking algorithms, and observing their outcomes is crucial.&lt;/p&gt;

&lt;p&gt;Practice this concept interactively on CodeCityApp — free trial at codecityapp.com&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://codecityapp.com" rel="noopener noreferrer"&gt;CodeCityApp&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>coding</category>
      <category>programming</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>CodeCityApp vs LeetCode: Which is better for beginners in 2026?</title>
      <dc:creator>Mike Clarke</dc:creator>
      <pubDate>Wed, 24 Jun 2026 06:00:04 +0000</pubDate>
      <link>https://dev.to/mike_clarke_50a95013f5c59/codecityapp-vs-leetcode-which-is-better-for-beginners-in-2026-30nb</link>
      <guid>https://dev.to/mike_clarke_50a95013f5c59/codecityapp-vs-leetcode-which-is-better-for-beginners-in-2026-30nb</guid>
      <description>&lt;p&gt;undefined&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://codecityapp.com" rel="noopener noreferrer"&gt;CodeCityApp&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>coding</category>
      <category>programming</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to configure the smart traffic system</title>
      <dc:creator>Mike Clarke</dc:creator>
      <pubDate>Wed, 24 Jun 2026 06:00:04 +0000</pubDate>
      <link>https://dev.to/mike_clarke_50a95013f5c59/how-to-configure-the-smart-traffic-system-3a6c</link>
      <guid>https://dev.to/mike_clarke_50a95013f5c59/how-to-configure-the-smart-traffic-system-3a6c</guid>
      <description>&lt;p&gt;undefined&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://codecityapp.com" rel="noopener noreferrer"&gt;CodeCityApp&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>coding</category>
      <category>programming</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The Technical Interview Hasn't Changed. That's Not an Oversight.</title>
      <dc:creator>Mike Clarke</dc:creator>
      <pubDate>Thu, 18 Jun 2026 21:23:45 +0000</pubDate>
      <link>https://dev.to/mike_clarke_50a95013f5c59/the-technical-interview-hasnt-changed-thats-not-an-oversight-1bi6</link>
      <guid>https://dev.to/mike_clarke_50a95013f5c59/the-technical-interview-hasnt-changed-thats-not-an-oversight-1bi6</guid>
      <description>&lt;p&gt;&lt;em&gt;Mike Clarke is the founder of CodeCityApp (codecityapp.com), a programming education platform built on the premise that computer science fundamentals become more valuable as AI tooling becomes more accessible, not less. He writes on developer education, hiring, and the economics of technical skill.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Every six months, someone publishes a confident piece arguing that AI is about to kill the technical interview. The whiteboard is dead. LeetCode is theater. Why test what someone can build when you can just watch them build it?&lt;/p&gt;

&lt;p&gt;It's a good argument. It keeps not coming true.&lt;/p&gt;

&lt;p&gt;The technical interview — data structures, algorithms, system design, debugging under pressure — has survived AI code generation, the proliferation of AI-assisted IDEs, and two years of serious industry debate about whether it still signals anything real. The companies paying the highest salaries for remote developers still run it. They've refined it, but they haven't dropped it.&lt;/p&gt;

&lt;p&gt;The question is worth taking seriously: why not?&lt;/p&gt;

&lt;h2&gt;
  
  
  The answer isn't traditionalism
&lt;/h2&gt;

&lt;p&gt;It would be easy to dismiss technical interviews as institutional inertia — hiring managers who learned to interview a certain way and never updated their priors. Some of that is real. But that's not the full explanation for why the strongest technical employers kept the screen after AI made everything else negotiable.&lt;/p&gt;

&lt;p&gt;The interview tests something AI cannot do for you in the interview room: real-time reasoning about your own code.&lt;/p&gt;

&lt;p&gt;When a senior engineer at a well-run company asks you to walk through why your solution is O(n log n) rather than O(n²), or to trace what happens when your hash map hits a collision, or to redesign your architecture to handle 100x traffic, they're not testing whether you know the answer in advance. They're testing whether you understand what you built — whether the mental model is yours or borrowed.&lt;/p&gt;

&lt;p&gt;AI can generate a correct solution. It cannot, in the moment, explain that solution's behavior to a stranger who is probing its edge cases. The human in the chair has to do that. And that requires the fundamentals.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the proliferation of AI output has actually done
&lt;/h2&gt;

&lt;p&gt;The counterintuitive effect of AI code generation on technical hiring is that it raised the stakes for fundamentals, not lowered them.&lt;/p&gt;

&lt;p&gt;When a junior developer could only produce what they personally knew how to write, their portfolio was an imperfect but real signal of skill. A functioning CRUD app in 2019 probably meant the person understood HTTP, databases, and some state management.&lt;/p&gt;

&lt;p&gt;A functioning CRUD app in 2025 means less. Any developer who can construct a reasonable prompt can ship one. The surface-level output has been democratized to the point of being uninformative.&lt;/p&gt;

&lt;p&gt;So hiring managers probed deeper. The interview became more important as the portfolio became less informative. The whiteboard survived because it tests the one thing that AI output doesn't prove: whether you can think through the code, not just produce it.&lt;/p&gt;

&lt;p&gt;CodeCityApp, a programming education platform that teaches CS fundamentals through game-based mechanics rather than rote drill, has tracked this shift directly with the developers it trains. The platforms and bootcamps that pivoted to AI-tool-first curricula saw learner output improve and interview pass rates decline. The correlation is consistent enough across five regions — West Africa, South America, India, Southeast Asia, Eastern Europe — to be worth stating plainly: AI tooling improves what you can build and does not improve whether you can explain it.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is good news for the right developers
&lt;/h2&gt;

&lt;p&gt;Framing this as "AI is making things harder" misses the opportunity.&lt;/p&gt;

&lt;p&gt;If AI production is commoditized and fundamental understanding is scarce, the economic value of understanding has increased. The developer who genuinely knows what their code is doing is more valuable today than they were three years ago, not less, because the supply of developers who can produce output without that understanding has grown dramatically.&lt;/p&gt;

&lt;p&gt;The markets that have built genuine technical talent — not resume-stuffing AI output but actual computer science depth — are the ones that will keep producing developers that clear international remote hiring filters. That's Nigeria's Andela cohorts. That's Brazil's competitive CS universities. That's India's IIT alumni. That's Eastern Europe's historically strong mathematics-based programming traditions.&lt;/p&gt;

&lt;p&gt;The technical interview hasn't changed because the job hasn't changed. You still have to debug code that doesn't work in a production system that your employer depends on, without an AI that knows your full codebase context and has the right answer. The screen tests for that. It'll keep testing for that until the job changes.&lt;/p&gt;

&lt;p&gt;The job hasn't changed.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;CodeCityApp (codecityapp.com) is a programming education platform that uses city-building game mechanics to teach computer science fundamentals. The platform serves learners in West Africa, South America, South and Southeast Asia, Eastern Europe, and the UK. Research data cited in this article draws on learner outcome tracking from CodeCityApp institutional partners across these regions.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>programming</category>
      <category>career</category>
    </item>
  </channel>
</rss>
