<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://dwhenry.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://dwhenry.github.io/" rel="alternate" type="text/html" /><updated>2026-09-17T08:16:49+00:00</updated><id>https://dwhenry.github.io/feed.xml</id><title type="html">David Henry</title><subtitle>Fixing that thing I broke last week.</subtitle><entry><title type="html">A background job hiding three conditionals deep</title><link href="https://dwhenry.github.io/2026/09/17/a-background-job-hiding-three-conditionals-deep/" rel="alternate" type="text/html" title="A background job hiding three conditionals deep" /><published>2026-09-17T00:00:00+00:00</published><updated>2026-09-17T00:00:00+00:00</updated><id>https://dwhenry.github.io/2026/09/17/a-background-job-hiding-three-conditionals-deep</id><content type="html" xml:base="https://dwhenry.github.io/2026/09/17/a-background-job-hiding-three-conditionals-deep/"><![CDATA[<p>Some bugs announce themselves. This one didn’t — it took months, a suite that kept growing, and one very confused stray row in an unrelated test’s count before anyone could say for certain what was going on.</p>

<h2 id="the-problem-a-job-enqueue-three-conditionals-deep">The problem: a job enqueue three conditionals deep</h2>

<p>Somewhere inside converting a lead into a subscription plan, there’s a helper called <code class="language-plaintext highlighter-rouge">enqueueInvoiceRequest</code>. You don’t get to it directly — you get to it by going through <code class="language-plaintext highlighter-rouge">convertProduct</code>, which is itself dispatched to from a product-type switch one layer up, and even once you’re inside it you still have to clear a legacy-plan early return, a brand check, an invoice-count guard, and a null check on a webhook request ID before the enqueue itself ever runs:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// server/src/services/lead-conversion/plan/index.ts</span>
<span class="k">if </span><span class="p">(</span><span class="nx">attributes</span><span class="p">.</span><span class="nx">brandId</span> <span class="o">===</span> <span class="nx">BRAND_ID</span><span class="p">.</span><span class="nx">acme</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">await</span> <span class="nf">enqueueInvoiceRequest</span><span class="p">({</span> <span class="nx">plan</span><span class="p">,</span> <span class="nx">trx</span> <span class="p">});</span>
<span class="p">}</span>

<span class="c1">// ...inside enqueueInvoiceRequest:</span>
<span class="k">if </span><span class="p">(</span><span class="nx">inboundWebhookRequestId</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// We need to wait for the transaction to commit before we can add the item to the queue</span>
  <span class="nf">attachToExecutionPromise</span><span class="p">(</span><span class="nx">trx</span><span class="p">,</span> <span class="dl">"</span><span class="s2">invoice-enqueue</span><span class="dl">"</span><span class="p">,</span> <span class="k">async </span><span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="k">await</span> <span class="nx">invoiceQueue</span><span class="p">.</span><span class="nf">addItem</span><span class="p">({</span>
      <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">create-invoice</span><span class="dl">"</span><span class="p">,</span>
      <span class="nx">inboundWebhookRequestId</span><span class="p">,</span>
      <span class="na">planId</span><span class="p">:</span> <span class="nx">plan</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
    <span class="p">});</span>
  <span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>

<p>That comment — <em>“we need to wait for the transaction to commit”</em> — is the whole problem in one line. The enqueue can’t run inside the transaction, because the transaction might still roll back and there’s no un-enqueueing a job. It also can’t run synchronously right after <code class="language-plaintext highlighter-rouge">trx.commit()</code>, because nothing at this depth of the call stack has a reference to “right after commit” — it’s three function calls and four conditionals away from whoever actually owns the transaction and decides when it commits.</p>

<h2 id="why-executionpromise-and-what-we-didnt-do-instead">Why executionPromise, and what we didn’t do instead</h2>

<p>Objection’s <code class="language-plaintext highlighter-rouge">Transaction</code> exposes <code class="language-plaintext highlighter-rouge">executionPromise</code>, which resolves once the transaction settles — commit or rollback, either way. Attaching to it with <code class="language-plaintext highlighter-rouge">.then()</code> gives you exactly the guarantee you need at exactly the point in the code where you need it, no matter how deep that point is:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">trx</span><span class="p">.</span><span class="nx">executionPromise</span><span class="p">.</span><span class="nf">then</span><span class="p">(</span><span class="nx">fn</span><span class="p">);</span>
</code></pre></div></div>

<p>That’s the entire mechanism. No return value has to travel back up through <code class="language-plaintext highlighter-rouge">enqueueInvoiceRequest</code> → <code class="language-plaintext highlighter-rouge">convertProduct</code> → the product-dispatch switch → whatever called that, just to tell the transaction’s owner “also run this once you’re done.” Every intermediate function stays exactly as it is.</p>

<p>We did consider the alternatives, briefly:</p>

<ul>
  <li><strong>Thread the deferred action back out through the return value</strong>, and let the code that actually owns the transaction run it after commit. This is the “correct” version in some abstract sense — no fire-and-forget, no ambient coupling to a transaction object passed four calls deep. It’s also a real cost: every function between the enqueue and the transaction’s owner would need its return type widened to carry “oh, and also run this afterwards,” purely to plumb one job dispatch through call sites that have nothing to do with it. Given that we had multiple use cases like this it just didn’t make sense.</li>
  <li><strong>Just delay it.</strong> Fire the job on a <code class="language-plaintext highlighter-rouge">setTimeout</code> a few hundred milliseconds after the write, on the assumption the transaction will have committed by then. This isn’t a guarantee, it’s a bet — one that gets worse under load, not better, since a slow commit and a fast timer are exactly the conditions you’d expect during the traffic spike you most need this to be correct for.</li>
</ul>

<p><code class="language-plaintext highlighter-rouge">executionPromise</code> won on both counts: it’s precise (it resolves on the actual event, not a guess about timing), and it’s local (the code that needs the deferral is the only code that has to know about it).</p>

<h2 id="the-ci-failure-that-got-worse-not-better">The CI failure that got worse, not better</h2>

<p>The trouble is that “fire-and-forget” and “test suite” don’t get along. The first sign of it was a commit back in April with an admirably honest message: <em>“Add a executionPromise callback has made test more flakey”</em> — the fix at the time was a global Mocha hook that stubbed the relevant client, wrapped its deferred callback in a 2-second timeout, and collected any errors to re-throw in a global <code class="language-plaintext highlighter-rouge">afterEach</code>. It made the symptom quieter without doing anything about the cause: a callback that isn’t awaited by anything can still be running, or about to run, after the test that triggered it has already finished and moved on.</p>

<p>It didn’t go away. It got worse, and predictably so, once we started rolling out transaction-based test isolation more broadly — every test now ran inside its own transaction that got rolled back at the end. That’s a good change on its own merits, but it also changed where a callback firing “late” actually landed: instead of writing to an already-cleaned-up database, it wrote straight into whatever other test happened to have its own transaction open at that exact moment, silently adding a row to whatever that test was counting, a row that wasn’t part of a transaction, and wouldn’t get rolled back. Small suites rarely lined up that unluckily. Running the full suite did, as a handful of count assertions that failed for no reason anyone could reproduce in isolation.</p>

<h2 id="reframing-executionpromise-as-a-background-job-not-a-callback">Reframing executionPromise as a background job, not a callback</h2>

<p>The thing that actually unlocked the fix was stepping back from “this is a callback attached to a transaction” and treating it as what it really is: background job execution, deferred past a boundary the test environment doesn’t otherwise care about. Once it’s framed that way, the fix looks exactly like how you’d handle any other job queue in tests — don’t run it for real, capture it, and let the test decide if and when to run it:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">const</span> <span class="nx">attachToExecutionPromise</span> <span class="o">=</span> <span class="p">(</span>
  <span class="nx">trx</span><span class="p">:</span> <span class="nx">Transaction</span><span class="p">,</span>
  <span class="nx">description</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
  <span class="nx">fn</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span><span class="p">,</span>
<span class="p">):</span> <span class="k">void</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">if </span><span class="p">(</span><span class="nx">ENV</span><span class="p">.</span><span class="nx">NODE_ENV</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">test</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">capturedCallbacks</span><span class="p">.</span><span class="nf">push</span><span class="p">({</span> <span class="nx">description</span><span class="p">,</span> <span class="nx">fn</span> <span class="p">});</span>
    <span class="k">return</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nx">trx</span><span class="p">.</span><span class="nx">executionPromise</span><span class="p">.</span><span class="nf">then</span><span class="p">(</span><span class="nx">fn</span><span class="p">);</span>
<span class="p">};</span>
</code></pre></div></div>

<p>Under test, nothing fires unawaited and nothing can outlive the test that triggered it — the callback just sits in an array until something asks for it by name, or the next test’s <code class="language-plaintext highlighter-rouge">beforeEach</code> clears it out. A test that cares can pull its own callback out with <code class="language-plaintext highlighter-rouge">getCapturedExecutionPromiseCallback('invoice-enqueue')</code> and run it deliberately, on its own schedule, same as it would call any other job handler directly.</p>

<p>It wasn’t a total, clean victory, though — worth saying, since that’s the more honest version of this story. One integration test, after adopting this fix, still leaked a row into unrelated tests at full-suite scale, from somewhere in the same request flow that this fix didn’t reach. We had a custom patch for it until we figure out a proper fix, and a comment saying as much, rather than a triumphant “and then everything was fine.” Sometimes the honest state of a fix is “this part’s solved, that part’s still open,” and it’s better to write that down than to pretend otherwise.</p>

<hr />

<p>The full source — <code class="language-plaintext highlighter-rouge">attachToExecutionPromise()</code>, the capture/flush test helpers, and the wiring into the test suite — is up in the <a href="/2026/09/17/a-background-job-hiding-three-conditionals-deep/code-reference/">complete code reference</a>, if you want the whole thing rather than the excerpts above.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Some bugs announce themselves. This one didn’t — it took months, a suite that kept growing, and one very confused stray row in an unrelated test’s count before anyone could say for certain what was going on.]]></summary></entry><entry><title type="html">A broken test suite, a tree and finding a rabbit</title><link href="https://dwhenry.github.io/2026/09/15/a-broken-test-suite-a-tree-and-finding-a-rabbit/" rel="alternate" type="text/html" title="A broken test suite, a tree and finding a rabbit" /><published>2026-09-15T00:00:00+00:00</published><updated>2026-09-15T00:00:00+00:00</updated><id>https://dwhenry.github.io/2026/09/15/a-broken-test-suite-a-tree-and-finding-a-rabbit</id><content type="html" xml:base="https://dwhenry.github.io/2026/09/15/a-broken-test-suite-a-tree-and-finding-a-rabbit/"><![CDATA[<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Caveat:wght@500;700&amp;family=Patrick+Hand&amp;display=swap" />

<figure class="ci-diary-sketch">
<style>
  .ci-diary-sketch { margin: 0 0 1.5rem; }
  .ci-diary-sketch svg { display: block; width: 100%; height: auto; border-radius: 8px; }
  .ci-diary-sketch figcaption { margin-top: 0.6rem; font-size: 0.85rem; color: var(--muted); }

@media (prefers-reduced-motion: reduce) {
.ci-diary-sketch .scene, .ci-diary-sketch .anim { animation-duration: .01ms !important; animation-iteration-count: 1 !important; }
}

.ci-diary-sketch .flash { fill: #ffffff; opacity: 0; }

.ci-diary-sketch .scene { animation-duration: 24s; animation-timing-function: linear; animation-iteration-count: infinite; }
.ci-diary-sketch .scene-idle { animation-name: idle-vis; }
.ci-diary-sketch .scene-ci { animation-name: ci-vis; opacity: 0; }
.ci-diary-sketch .scene-smash { animation-name: smash-vis; opacity: 0; }
.ci-diary-sketch .scene-sign { animation-name: sign-vis; opacity: 0; }

@keyframes idle-vis {
0%{opacity:1} 12.5%{opacity:1} 13.2%{opacity:0}
94%{opacity:0} 94.6%{opacity:1} 100%{opacity:1}
}
@keyframes ci-vis {
0%{opacity:0} 13.2%{opacity:0} 14%{opacity:1}
67.5%{opacity:1} 68.2%{opacity:0} 100%{opacity:0}
}
@keyframes smash-vis {
0%{opacity:0} 68.2%{opacity:0} 69%{opacity:1}
81.4%{opacity:1} 82.1%{opacity:0} 100%{opacity:0}
}
@keyframes sign-vis {
0%{opacity:0} 81.4%{opacity:0} 82.3%{opacity:1}
94%{opacity:1} 94.6%{opacity:0} 100%{opacity:0}
}
@keyframes flash-pulse {
0%,12.8%{opacity:0} 13.2%{opacity:.85} 13.6%{opacity:0}
67.8%{opacity:0} 68.2%{opacity:.85} 68.6%{opacity:0}
100%{opacity:0}
}
.ci-diary-sketch .flash { animation: flash-pulse 24s linear infinite; }

.ci-diary-sketch #bubble { animation: bubble-pop 24s linear infinite; transform-box: fill-box; transform-origin: 50% 100%; }
@keyframes bubble-pop {
0%,4%{opacity:0; transform:scale(.5)} 6%{opacity:1; transform:scale(1)}
11.2%{opacity:1; transform:scale(1)} 12.5%{opacity:0; transform:scale(.5)} 100%{opacity:0; transform:scale(.5)}
}
.ci-diary-sketch .type-bounce { animation: type-bounce .46s ease-in-out infinite; transform-box: fill-box; transform-origin: 50% 0%; }
.ci-diary-sketch #hand-right { animation-delay: .23s; }
@keyframes type-bounce { 0%,100%{transform:translateY(0)} 50%{transform:translateY(7px)} }

.ci-diary-sketch #bar-fill { fill:#2f9e58; transform-box:fill-box; transform-origin:0% 50%; animation: bar-fill 24s linear infinite; }
@keyframes bar-fill {
0%,14%{ transform:scaleX(0); fill:#2f9e58 }
27%{ transform:scaleX(.75); fill:#2f9e58 }
27.3%{ transform:scaleX(.75); fill:#e0483f }
29.4%{ transform:scaleX(.75); fill:#e0483f }
30.2%{ transform:scaleX(0); fill:#2f9e58 }
31.7%{ transform:scaleX(0); fill:#2f9e58 }
45%{ transform:scaleX(.60); fill:#2f9e58 }
45.3%{ transform:scaleX(.60); fill:#e0483f }
47.7%{ transform:scaleX(.60); fill:#e0483f }
48.4%{ transform:scaleX(0); fill:#2f9e58 }
50%{ transform:scaleX(0); fill:#2f9e58 }
65%{ transform:scaleX(.99); fill:#2f9e58 }
65.3%{ transform:scaleX(.99); fill:#e0483f }
67.7%{ transform:scaleX(.99); fill:#e0483f }
68.3%{ transform:scaleX(0); fill:#2f9e58 }
100%{ transform:scaleX(0); fill:#2f9e58 }
}
.ci-diary-sketch #fail-1, .ci-diary-sketch #fail-2, .ci-diary-sketch #fail-3 { opacity: 0; }
.ci-diary-sketch #fail-1 { animation: fail-1 24s linear infinite; }
.ci-diary-sketch #fail-2 { animation: fail-2 24s linear infinite; }
.ci-diary-sketch #fail-3 { animation: fail-3 24s linear infinite; }
@keyframes fail-1 { 0%,27.2%{opacity:0} 27.5%{opacity:1} 29.4%{opacity:1} 30%{opacity:0} 100%{opacity:0} }
@keyframes fail-2 { 0%,45.2%{opacity:0} 45.5%{opacity:1} 47.7%{opacity:1} 48.2%{opacity:0} 100%{opacity:0} }
@keyframes fail-3 { 0%,65.2%{opacity:0} 65.5%{opacity:1} 67.7%{opacity:1} 68.1%{opacity:0} 100%{opacity:0} }

.ci-diary-sketch #retry-btn { opacity: 0; animation: retry-vis 24s linear infinite; }
@keyframes retry-vis {
0%,27.4%{opacity:0} 27.6%{opacity:1} 30.4%{opacity:1} 30.9%{opacity:0}
45.4%{opacity:0} 45.6%{opacity:1} 48.6%{opacity:1} 49.1%{opacity:0}
100%{opacity:0}
}
.ci-diary-sketch #cursor { opacity: 0; animation: cursor-move 24s linear infinite; transform-box: fill-box; transform-origin: 50% 50%; }
@keyframes cursor-move {
0%,28%{opacity:0; transform:translate(140px,-70px) scale(1)}
28.6%{opacity:1; transform:translate(0,0) scale(1)}
29.6%{opacity:1; transform:translate(0,0) scale(.8)}
30%{opacity:1; transform:translate(0,0) scale(1)}
30.6%{opacity:0; transform:translate(0,0) scale(1)}
46.2%{opacity:0; transform:translate(140px,-70px) scale(1)}
46.8%{opacity:1; transform:translate(0,0) scale(1)}
47.8%{opacity:1; transform:translate(0,0) scale(.8)}
48.2%{opacity:1; transform:translate(0,0) scale(1)}
48.8%{opacity:0; transform:translate(0,0) scale(1)}
100%{opacity:0}
}

.ci-diary-sketch #shake-group { animation: shake 24s linear infinite; }
@keyframes shake {
0%,76.6%{ transform:translate(0,0) }
76.8%{ transform:translate(-6px,2px) } 77%{ transform:translate(7px,-3px) }
77.2%{ transform:translate(-5px,3px) } 77.4%{ transform:translate(4px,-2px) }
77.7%{ transform:translate(0,0) } 100%{ transform:translate(0,0) }
}
.ci-diary-sketch #bat-arm { transform-origin: 283px 224px; animation: bat-swing 24s linear infinite; }
@keyframes bat-swing {
0%,69.3%{ transform:rotate(0deg) } 71%{ transform:rotate(-58deg) } 75%{ transform:rotate(-58deg) }
76.9%{ transform:rotate(38deg) } 78%{ transform:rotate(20deg) } 80.5%{ transform:rotate(20deg) }
81.4%{ transform:rotate(0deg) } 100%{ transform:rotate(0deg) }
}
.ci-diary-sketch #impact { opacity: 0; animation: impact 24s linear infinite; }
@keyframes impact { 0%,76.7%{opacity:0} 77%{opacity:1} 77.6%{opacity:1} 78.3%{opacity:0} 100%{opacity:0} }
.ci-diary-sketch #crack { opacity: 0; animation: crack 24s linear infinite; }
@keyframes crack { 0%,76.9%{opacity:0} 77.1%{opacity:1} 81.4%{opacity:1} 81.6%{opacity:0} 100%{opacity:0} }

.ci-diary-sketch #sign-inner { transform-box: fill-box; transform-origin: 50% 50%; animation: sign-pop 24s linear infinite; }
@keyframes sign-pop {
0%,82.1%{ transform:scale(.6) rotate(-3deg) } 82.6%{ transform:scale(1.06) rotate(-3deg) }
83%{ transform:scale(1) rotate(-3deg) } 94%{ transform:scale(1) rotate(-3deg) } 100%{ transform:scale(.6) rotate(-3deg) }
}
</style>
<svg viewBox="0 0 800 500" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="A rough sketch of a stick figure typing happily, watching a CI build fail at 75%, 60%, then 99% across three retries, smashing the computer with a bat, and holding up a sign that says tests getting you down, before looping.">
<defs>
<filter id="sketch" x="-30%" y="-30%" width="160%" height="160%">
<feTurbulence type="fractalNoise" baseFrequency="0.018" numOctaves="2" seed="7" result="noise" />
<feDisplacementMap in="SourceGraphic" in2="noise" scale="4" />
</filter>
<pattern id="dots" width="26" height="26" patternUnits="userSpaceOnUse">
<circle cx="2" cy="2" r="1.3" fill="#d7dee6" />
</pattern>
</defs>

  <rect width="800" height="500" fill="#f2f5f7" />
  <rect width="800" height="500" fill="url(#dots)" />

  <!-- ============ SCENE 1: idle typing (side view) ============ -->
  <g class="scene scene-idle">
    <g class="sketch" fill="none" stroke="#2b3440" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
      <rect x="230" y="330" width="340" height="14" rx="3" fill="#f7f9fb" />
      <rect x="246" y="344" width="10" height="74" />
      <rect x="540" y="344" width="10" height="74" />
      <path d="M392 186 L400 186 Q432 190 428 239 Q432 288 400 292 L392 292 Z" fill="#f7f9fb" />
      <rect x="336" y="314" width="128" height="16" rx="3" />
      <rect x="398" y="292" width="14" height="26" fill="#f7f9fb" />
      <path d="M382 318 L438 318 L432 330 L388 330 Z" fill="#f7f9fb" />
    </g>

    <g id="bubble" transform="translate(-120,0)">
      <circle cx="486" cy="196" r="4" class="sketch" fill="none" stroke="#2b3440" stroke-width="2" />
      <circle cx="500" cy="182" r="7" class="sketch" fill="none" stroke="#2b3440" stroke-width="2" />
      <path class="sketch" d="M498 168 q-16 -18 4 -26 q10 -14 28 -8 q18 -10 30 4 q18 -2 20 16 q10 12 -4 22 q-4 14 -22 12 q-16 10 -30 -2 q-20 4 -26 -18 Z" fill="#f7f9fb" stroke="#2b3440" stroke-width="3" />
      <path d="M514 150 l12 12 l22 -24" fill="none" stroke="#2f9e58" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" class="sketch" />
    </g>

    <!-- chair -->
    <g class="sketch" fill="none" stroke="#2b3440" stroke-width="3" stroke-linecap="round">
      <path d="M248 218 L248 306" />
      <path d="M248 302 L292 302" />
    </g>

    <!-- person, profile facing the desk, head-to-hip ~ head-diameter x2 -->
    <g class="sketch" fill="none" stroke="#2b3440" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
      <circle cx="280" cy="200" r="20" fill="#f2f5f7" />
      <path d="M261 189 q-8 -12 6 -16" />
      <path d="M268 184 q-2 -10 10 -10" />
      <path d="M300 197 L308 201 L300 206" />
      <path d="M280 220 L286 300" />
    </g>

    <g class="sketch" fill="none" stroke="#2b3440" stroke-width="3" stroke-linecap="round">
      <path d="M283 224 L316 248" />
    </g>
    <g class="type-bounce" id="hand-left">
      <path class="sketch" d="M316 248 L360 276" fill="none" stroke="#2b3440" stroke-width="3" stroke-linecap="round" />
    </g>
    <g class="sketch" fill="none" stroke="#2b3440" stroke-width="3" stroke-linecap="round">
      <path d="M283 230 L322 256" />
    </g>
    <g class="type-bounce" id="hand-right">
      <path class="sketch" d="M322 256 L372 288" fill="none" stroke="#2b3440" stroke-width="3" stroke-linecap="round" />
    </g>

  </g>

  <!-- ============ SCENE 2: CI zoom ============ -->
  <g class="scene scene-ci">
    <g class="sketch" fill="none" stroke="#2b3440" stroke-width="4" stroke-linecap="round" stroke-linejoin="round">
      <rect x="70" y="50" width="660" height="400" rx="8" fill="#f7f9fb" />
    </g>
    <g class="sketch" fill="none" stroke="#6b7784" stroke-width="2">
      <rect x="100" y="82" width="600" height="290" fill="#eef1f4" />
    </g>
    <g class="sketch" stroke="#2b3440" stroke-width="2">
      <circle cx="126" cy="104" r="6" fill="#e0483f" />
      <circle cx="146" cy="104" r="6" fill="#ffd23f" />
      <circle cx="166" cy="104" r="6" fill="#2f9e58" />
    </g>
    <text x="400" y="112" text-anchor="middle" font-family="'Patrick Hand',cursive" font-size="19" fill="#6b7784" letter-spacing="1">ci · build pipeline</text>

    <rect class="sketch" x="200" y="230" width="400" height="30" rx="4" fill="none" stroke="#2b3440" stroke-width="3" />
    <rect id="bar-fill" x="200" y="230" width="400" height="30" rx="2" />

    <g id="fail-1"><text x="400" y="302" text-anchor="middle" font-family="'Caveat',cursive" font-weight="700" font-size="34" fill="#e0483f" transform="rotate(-2 400 290)">failed — 75%!</text></g>
    <g id="fail-2"><text x="400" y="302" text-anchor="middle" font-family="'Caveat',cursive" font-weight="700" font-size="34" fill="#e0483f" transform="rotate(-2 400 290)">failed — 60%!</text></g>
    <g id="fail-3"><text x="400" y="302" text-anchor="middle" font-family="'Caveat',cursive" font-weight="700" font-size="34" fill="#e0483f" transform="rotate(-2 400 290)">failed — 99%!!</text></g>

    <g id="retry-btn">
      <rect class="sketch" x="325" y="330" width="150" height="48" rx="6" fill="#f7f9fb" stroke="#3b6fd9" stroke-width="3" />
      <text x="400" y="362" text-anchor="middle" font-family="'Caveat',cursive" font-weight="700" font-size="24" fill="#3b6fd9">retry?</text>
    </g>

    <g id="cursor" class="sketch">
      <path d="M400 355 L400 384 L406 377 L412 388 L417 385 L411 375 L420 373 Z" fill="#2b3440" stroke="#2b3440" stroke-width="1" />
    </g>

  </g>

  <!-- ============ SCENE 3: smash (side view) ============ -->
  <g class="scene scene-smash">
    <g id="shake-group">
      <g class="sketch" fill="none" stroke="#2b3440" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
        <rect x="230" y="330" width="340" height="90" rx="4" />
        <path d="M392 186 L400 186 Q432 190 428 239 Q432 288 400 292 L392 292 Z" fill="#f7f9fb" />
        <rect x="336" y="342" width="128" height="16" rx="3" />
        <rect x="398" y="292" width="14" height="26" fill="#f7f9fb" />
        <path d="M382 318 L438 318 L432 330 L388 330 Z" fill="#f7f9fb" />
      </g>
      <path id="crack" class="sketch" d="M400 200 L418 222 L408 232 L422 248 L410 260 L424 276" fill="none" stroke="#e0483f" stroke-width="3" stroke-linejoin="round" />
      <path id="impact" class="sketch" d="M410 162 L418 142 L424 164 L442 148 L432 170 L454 168 L434 182 L450 196 L428 188 L430 210 L416 192 L400 206 L404 184 L384 188 L402 174 Z" fill="#ffd23f" stroke="#2b3440" stroke-width="2" />
      <text id="impact-txt" x="438" y="136" text-anchor="middle" font-family="'Caveat',cursive" font-weight="700" font-size="28" fill="#2b3440" transform="rotate(6 438 136)" opacity="0" style="animation:impact 24s linear infinite">smash!</text>

      <!-- person, profile, standing to swing -->
      <g class="sketch" fill="none" stroke="#2b3440" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
        <circle cx="280" cy="195" r="20" fill="#f2f5f7" />
        <path d="M261 184 q-8 -12 6 -16" />
        <path d="M268 179 q-2 -10 10 -10" />
        <path d="M300 192 L308 196 L300 201" />
        <path d="M280 215 L288 300" />
        <path d="M285 300 L270 330" />
        <path d="M291 300 L306 330" />
      </g>

      <g id="bat-arm" class="sketch" stroke="#2b3440" stroke-width="3" stroke-linecap="round">
        <path d="M283 224 L326 206" fill="none" />
        <circle cx="326" cy="206" r="6" fill="#f2f5f7" />
        <path d="M326 206 L398 168" fill="none" stroke="#c99a5b" stroke-width="9" />
        <ellipse cx="400" cy="167" rx="11" ry="8" fill="#8a5a2b" stroke="#2b3440" stroke-width="2" transform="rotate(-27 400 167)" />
      </g>
    </g>

  </g>

  <!-- ============ SCENE 4: sign ============ -->
  <g class="scene scene-sign">
    <g id="sign-inner">
      <rect class="sketch" x="210" y="150" width="380" height="200" rx="4" fill="#f7f9fb" stroke="#2b3440" stroke-width="4" />
      <text x="400" y="230" text-anchor="middle" font-family="'Caveat',cursive" font-weight="700" font-size="42" fill="#2b3440">ci getting</text>
      <text x="400" y="280" text-anchor="middle" font-family="'Caveat',cursive" font-weight="700" font-size="42" fill="#2b3440">you down?</text>
      <path class="sketch" d="M300 288 q100 14 200 0" fill="none" stroke="#e0483f" stroke-width="3" />
      <text x="400" y="322" text-anchor="middle" font-family="'Patrick Hand',cursive" font-size="18" fill="#6b7784">— just never merge</text>
    </g>
  </g>

  <rect class="flash" width="800" height="500" />
</svg>
<figcaption>CI Fail</figcaption>
</figure>

<p>I’m not always the best at leaving well enough alone. For years our test suite cleaned up after itself with all the elegance of a toddler tidying a bedroom by sweeping everything under the rug: when needed, we called <code class="language-plaintext highlighter-rouge">clearDatabase()</code> and truncated every table. Simple. Confident. Wrong, not in the dramatic “everything’s on fire” sense, but a slow death by a thousand cuts — one that had apparently crept up to failing around 50% of test runs by the time I found out, which someone mentioned to me only once I was already elbow-deep in replacing it.</p>

<h2 id="the-problem-wed-been-living-with">The problem we’d been living with</h2>

<p><code class="language-plaintext highlighter-rouge">clearDatabase()</code> had been there so long it had achieved a sort of tenure — untouchable, load-bearing, the kind of code nobody wants to be the one who breaks. Previous attempts had pruned the easy wins, but the mountain still loomed. It “worked,” in the sense that the tests mostly ran and mostly went green, which is a very low bar we had somehow decided was good enough. As the suite grew into the thousands of tests, two things kept nagging at me, each more embarrassing than the last:</p>

<ul>
  <li><strong>It didn’t actually guarantee isolation — it just <em>looked</em> like it did.</strong> <code class="language-plaintext highlighter-rouge">clearDatabase()</code> only ran <em>when called</em>, which is a generous way of saying it ran whenever someone remembered to call it. Two tests in the same <code class="language-plaintext highlighter-rouge">describe</code> regularly depended on each other’s leftovers, and in fact plenty of tests had come to depend on exactly that — meaning they couldn’t be run in isolation without falling over, which rather defeats the point of calling it “isolation” in the first place.</li>
  <li><strong>And that’s exactly what caused the flakiness.</strong> Because isolation only ever existed on paper, whether a test passed depended on what had run before it, in what order, and under what conditions — which is a fancy way of saying it was basically luck. Locally, with everyone running a handful of files at a time, that luck mostly held. Under CI, with the full suite running in whatever order and however much parallelism it decided to use that day, the luck ran out — and because nothing about this was subtle, a single test’s hidden dependency on another test’s leftovers would cheerfully cascade into a dozen unrelated failures, none of which had anything to do with the code actually being tested.</li>
</ul>

<p>That last one is the kind of bug that quietly kills trust in your own test suite. A red build that’s “probably nothing, just re-run it” is worse than no test suite at all — at least an empty suite doesn’t lie to you. Eventually nobody looks at red builds anymore, and at that point you don’t have a safety net, you have a very expensive gut feeling.</p>

<h2 id="reaching-for-a-feature-that-was-already-there">Reaching for a feature that was already there</h2>

<p>This didn’t start as “let’s rewrite how the test suite cleans up.” It started as a bug hunt — the cascade failures above needed a root cause, and the obvious first move was to see whether anyone else had already solved this. They had: Postgres transactions, rolled back instead of committed, are a well-known pattern for exactly this problem, and there’s an existing library for it — <a href="https://github.com/romeerez/pg-transactional-tests">pg-transactional-tests</a> — that wraps each test in a transaction against the <code class="language-plaintext highlighter-rouge">pg</code> package directly.</p>

<p>It didn’t fit us. The library assumes a fairly clean hook structure — transaction opens, test runs, transaction rolls back — and our suite doesn’t play by those rules. We have describe blocks with real database writes happening in <code class="language-plaintext highlighter-rouge">before()</code>, not just <code class="language-plaintext highlighter-rouge">beforeEach()</code>, which a purely per-test wrapper would either miss entirely or roll back at the wrong time. And in more places than I’d like to admit, tests were quietly relying on state a previous test had left behind — which a library built around strict per-test isolation would break in ways that looked like the library’s fault, not ours.</p>

<p>So more of this was failure than success at first: the off-the-shelf fix didn’t fit our shape of problem, and untangling <em>why</em> it didn’t fit taught us more about the suite’s hidden assumptions than the fix itself ever did. What we ended up building — <code class="language-plaintext highlighter-rouge">transactionPerTest()</code> and <code class="language-plaintext highlighter-rouge">transactionPerDescribe()</code> — is the same core idea as that library, just built to cope with our <code class="language-plaintext highlighter-rouge">before()</code>-heavy, occasionally state-sharing suite instead of assuming a cleaner one.</p>

<p><code class="language-plaintext highlighter-rouge">transactionPerTest()</code> now wraps every <code class="language-plaintext highlighter-rouge">it()</code> globally via <code class="language-plaintext highlighter-rouge">beforeEach</code>/<code class="language-plaintext highlighter-rouge">afterEach</code>. In the common case, you don’t call anything yourself — you just write a normal, independent test, and the rollback happens for free underneath you.</p>

<p>That sentence undersells how much work it took to get there. But — as with most “just use the database properly” fixes — the real story is in the edge cases it surfaced once it was actually running against our suite.</p>

<h2 id="edge-case-one-a-before-that-runs-before-any-transaction-exists">Edge case one: a <code class="language-plaintext highlighter-rouge">before()</code> that runs before any transaction exists</h2>

<p>This one cost us a real, silently-leaking row before we understood it.</p>

<p><code class="language-plaintext highlighter-rouge">transactionPerTest()</code> opens and rolls back its transaction from <code class="language-plaintext highlighter-rouge">beforeEach</code>/<code class="language-plaintext highlighter-rouge">afterEach</code>, which Mocha runs <em>per test</em>. A describe-level <code class="language-plaintext highlighter-rouge">before()</code> runs once, and — crucially — runs before the first <code class="language-plaintext highlighter-rouge">beforeEach</code> of that suite fires. Which means: if a <code class="language-plaintext highlighter-rouge">before()</code> writes to the database directly, and nothing further up the chain has already opened a transaction, that write goes straight to the real connection. There’s no transaction for <code class="language-plaintext highlighter-rouge">afterEach</code> to roll back, so the row just… stays. Forever, or until someone notices.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ❌ Leaks a real, permanent row — before() runs before any</span>
<span class="c1">// per-test transaction exists, so this insert is never rolled back.</span>
<span class="nf">describe</span><span class="p">(</span><span class="dl">"</span><span class="s2">LpaCase#willsuiteStatus</span><span class="dl">"</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">let</span> <span class="na">lpaCase</span><span class="p">:</span> <span class="nx">LpaCase</span><span class="p">;</span>

  <span class="nf">before</span><span class="p">(</span><span class="k">async </span><span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">lpaCase</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">lpaCaseFactory</span><span class="p">({</span> <span class="na">status</span><span class="p">:</span> <span class="dl">"</span><span class="s2">in_progress</span><span class="dl">"</span> <span class="p">});</span>
  <span class="p">});</span>

  <span class="nf">it</span><span class="p">(</span><span class="dl">"</span><span class="s2">returns the mapped status for in_progress</span><span class="dl">"</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nf">expect</span><span class="p">(</span><span class="nx">lpaCase</span><span class="p">.</span><span class="nx">willsuiteStatus</span><span class="p">).</span><span class="nx">to</span><span class="p">.</span><span class="nf">equal</span><span class="p">(</span><span class="dl">"</span><span class="s2">IN_PROGRESS</span><span class="dl">"</span><span class="p">);</span>
  <span class="p">});</span>
<span class="p">});</span>
</code></pre></div></div>

<p>The fix is <code class="language-plaintext highlighter-rouge">transactionPerDescribe()</code>, called as the first line of the block, so its own <code class="language-plaintext highlighter-rouge">before()</code> opens a transaction before the fixture-creating <code class="language-plaintext highlighter-rouge">before()</code> gets a chance to run (Mocha runs <code class="language-plaintext highlighter-rouge">before()</code> hooks outside-in, so this ordering is guaranteed):</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ✅ transactionPerDescribe()'s before() runs first, so the fixture</span>
<span class="c1">// is created inside a transaction and rolled back once the describe finishes.</span>
<span class="nf">describe</span><span class="p">(</span><span class="dl">"</span><span class="s2">LpaCase#willsuiteStatus</span><span class="dl">"</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nf">transactionPerDescribe</span><span class="p">();</span>

  <span class="kd">let</span> <span class="na">lpaCase</span><span class="p">:</span> <span class="nx">LpaCase</span><span class="p">;</span>

  <span class="nf">before</span><span class="p">(</span><span class="k">async </span><span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">lpaCase</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">lpaCaseFactory</span><span class="p">({</span> <span class="na">status</span><span class="p">:</span> <span class="dl">"</span><span class="s2">in_progress</span><span class="dl">"</span> <span class="p">});</span>
  <span class="p">});</span>

  <span class="nf">it</span><span class="p">(</span><span class="dl">"</span><span class="s2">returns the mapped status for in_progress</span><span class="dl">"</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nf">expect</span><span class="p">(</span><span class="nx">lpaCase</span><span class="p">.</span><span class="nx">willsuiteStatus</span><span class="p">).</span><span class="nx">to</span><span class="p">.</span><span class="nf">equal</span><span class="p">(</span><span class="dl">"</span><span class="s2">IN_PROGRESS</span><span class="dl">"</span><span class="p">);</span>
  <span class="p">});</span>
<span class="p">});</span>
</code></pre></div></div>

<p>By default this doesn’t remove per-test isolation, either — the global per-test wrapping just becomes a nested savepoint on top of the describe’s transaction, so individual tests still can’t leak into each other. It only changes where the <em>shared</em> fixture setup lives. There’s an opt-in flag, <code class="language-plaintext highlighter-rouge">skipIndividualTransaction</code>, that removes that per-test nesting entirely and lets tests deliberately share state — but that reintroduces exactly the ordering-dependent fragility we were trying to get rid of, so it’s a deliberate, rare choice, not a default.</p>

<p>We didn’t just write this down and hope people remembered — a <code class="language-plaintext highlighter-rouge">before()</code> leaking a real row was exactly the kind of thing everyone agrees is bad and then does anyway six months later, so there’s now a custom ESLint rule (<code class="language-plaintext highlighter-rouge">require-transaction-wrapper-for-before</code>) that flags any describe-level <code class="language-plaintext highlighter-rouge">before()</code> without <code class="language-plaintext highlighter-rouge">transactionPerDescribe()</code> or <code class="language-plaintext highlighter-rouge">skipTransactionWrapping()</code> somewhere in its own or an ancestor describe. A <code class="language-plaintext highlighter-rouge">before()</code> that never touches the database can opt out with a plain disable comment — but by default, the lint rule assumes guilty until proven innocent.</p>

<h2 id="edge-case-two-when-a-test-needs-a-real-aborted-transaction">Edge case two: when a test needs a real, aborted transaction</h2>

<p>Postgres aborts an <em>entire</em> transaction on any query error — not just the failing query. Every later query on that connection fails with “current transaction is aborted” until something rolls back, in full or to a savepoint. A <code class="language-plaintext highlighter-rouge">try</code>/<code class="language-plaintext highlighter-rouge">catch</code> around the failing query doesn’t save you from this on its own.</p>

<p>That’s a real problem once every test is already running inside an ambient transaction: a test that deliberately triggers and recovers from a DB-level error (say, a unique constraint violation) can take down every query that runs afterwards in that test, or worse, in a later one on the same connection.</p>

<p>The fix, <code class="language-plaintext highlighter-rouge">runInSavepoint()</code>, runs the risky call inside its own nested savepoint on top of whatever transaction is already active, and only rolls back <em>that</em>, leaving the ambient transaction healthy:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">try</span> <span class="p">{</span>
  <span class="k">await</span> <span class="nf">runInSavepoint</span><span class="p">(()</span> <span class="o">=&gt;</span>
    <span class="nx">Country</span><span class="p">.</span><span class="nf">query</span><span class="p">().</span><span class="nf">insert</span><span class="p">({</span> <span class="na">id</span><span class="p">:</span> <span class="nx">duplicateId</span><span class="p">,</span> <span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Second</span><span class="dl">"</span><span class="p">,</span> <span class="na">code</span><span class="p">:</span> <span class="dl">"</span><span class="s2">ZZ-D2</span><span class="dl">"</span> <span class="p">}),</span>
  <span class="p">);</span>
<span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// the savepoint was rolled back, not the ambient (per-test) transaction —</span>
  <span class="c1">// so this query still works instead of failing with</span>
  <span class="c1">// "current transaction is aborted"</span>
  <span class="nf">expect</span><span class="p">(</span><span class="k">await</span> <span class="nx">Country</span><span class="p">.</span><span class="nf">query</span><span class="p">().</span><span class="nf">findOne</span><span class="p">({</span> <span class="na">code</span><span class="p">:</span> <span class="dl">"</span><span class="s2">ZZ-D1</span><span class="dl">"</span> <span class="p">})).</span><span class="nx">to</span><span class="p">.</span><span class="nx">exist</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>It’s not just a testing trick, either — it’s the same pattern our production code already uses to catch a constraint violation mid-request without taking the whole request down.</p>

<p>For the handful of cases where none of this is enough — tests that need real, independent transactions racing each other (proving row locking works, for instance) — there’s <code class="language-plaintext highlighter-rouge">skipTransactionWrapping(reason)</code>, which opts a block out entirely and falls back to a <code class="language-plaintext highlighter-rouge">clearDatabase()</code> safety net once it finishes. It’s the escape hatch, not the default, and it comes with a mandatory <code class="language-plaintext highlighter-rouge">reason</code> so nobody has to reverse-engineer <em>why</em> a block needed it six months later.</p>

<h2 id="edge-case-three-everything-happens-at-the-same-instant">Edge case three: everything happens at the same instant</h2>

<p>A smaller but more insidious one: rows created in the same test do end up with an identical <code class="language-plaintext highlighter-rouge">createdAt</code>, because Postgres’s <code class="language-plaintext highlighter-rouge">now()</code> is fixed for the whole transaction rather than the wall clock. Depending on where the ambiguity actually bites, the fix is either a monotonic <code class="language-plaintext highlighter-rouge">nextCreatedAt()</code> in the factory, a real id-based tie-breaker in production code where the id is a reliable insertion-order surrogate, or — usually the right call — just asserting on the row’s <code class="language-plaintext highlighter-rouge">id</code> instead of its position in an array. Not exotic, just another place the old truncate-based setup had been quietly hiding an assumption.</p>

<h2 id="what-id-tell-past-me">What I’d tell past me</h2>

<p>The tempting version of this story is “we replaced a flaky thing with a reliable thing.” The more honest version is that the replacement exposed assumptions our tests had been quietly making for years — about ordering, about hook timing, about what “isolated” actually meant — that a full table wipe had been papering over the whole time.</p>

<p>None of the fixes above were exotic. A monotonic counter. A documented hook-ordering gotcha. A savepoint instead of a full rollback. The hard part wasn’t the Postgres feature — it was noticing where our tests had been relying on <code class="language-plaintext highlighter-rouge">clearDatabase()</code>’s side effects without anyone writing that reliance down anywhere. If there’s a lesson in here, it’s the same one I keep relearning: the “obviously safe” cleanup step is usually hiding a few assumptions that are worth writing down before you rip it out, not after.</p>

<h2 id="the-rabbit-at-the-bottom-of-the-hole">The rabbit at the bottom of the hole</h2>

<p>Here’s the bit I keep coming back to. Partway through this, we hit a test that only ever failed once it was running inside a transaction — never before, never in isolation, only as part of the wider suite, only with rollback-based isolation switched on. That’s about as unhelpful a signal as a test can give you: the thing you just built to make failures more honest was itself producing one, and it wasn’t obvious whether the bug was in the test, the production code, or the new transaction plumbing.</p>

<p>It turned out to be a real bug — a genuine race in how state was shared between two things that used to be accidentally separated by <code class="language-plaintext highlighter-rouge">clearDatabase()</code>’s side effects, and were now sharing a transaction that made the collision visible for the first time. Not a bug in the new approach; a bug the new approach finally had the honesty to show us. I’m not entirely sure why that felt like such a big reveal at the time — it’s “a test caught a real bug,” which is the whole point of a test suite — but it did. Maybe because it was proof the whole exercise hadn’t just moved the flakiness somewhere else.</p>

<p>That’s the tree-and-rabbit of the title, really: VS Code’s worktrees meant this didn’t have to be my whole week to be worth chasing. I could spin the investigation off into its own worktree, on its own branch, and let it run as a side task while I stayed on my actual tickets in the main checkout — the kind of thing that, on paper, could have swallowed weeks of focus: a root-cause investigation, a rejected library, three edge cases, and a bug hiding under all of it. It still needed hand-holding, and it still went wrong more than once along the way — but that’s what experience (and <code class="language-plaintext highlighter-rouge">git revert</code>) is for. In practice it stayed a contained side quest of a couple of days, running alongside everything else, without taking the rest of the work off track. Whatever else I take from this, that’s the part worth remembering: the rabbit hole didn’t need to become the whole week.</p>

<hr />

<p>The full source for everything above — the transaction wrapper, the savepoint helper, the monotonic timestamp helper, and the ESLint rule — is up in the <a href="/2026/09/15/a-broken-test-suite-a-tree-and-finding-a-rabbit/code-reference/">complete code reference</a>, if you want to see it rather than take my word for it.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Dropbox root folder access</title><link href="https://dwhenry.github.io/2020/11/12/dropbox-root-folder-access/" rel="alternate" type="text/html" title="Dropbox root folder access" /><published>2020-11-12T00:00:00+00:00</published><updated>2020-11-12T00:00:00+00:00</updated><id>https://dwhenry.github.io/2020/11/12/dropbox-root-folder-access</id><content type="html" xml:base="https://dwhenry.github.io/2020/11/12/dropbox-root-folder-access/"><![CDATA[<p>I’m using a business dropbox account and have access to the teams shared folders through the various interfaces. Why then when using the API does it only show my personal folder when connecting via the dropbox API?</p>

<p>According to the <a href="https://www.dropbox.com/lp/developers/reference/dbx-team-files-guide#namespaces">dropbox documentation</a> it is to do with namespaces. It is also possible to override this behavious and set you namespace to be the root folder, giving you full access via the API that we get from the web interface. It’s not that hard, you just need to set a header and off you go.</p>

<p>Well, almost, first of all the <a href="https://github.com/Jesus/dropbox_api/pull/73">dropbox_api gem</a> will need to merge my PR, giving access to the <code class="language-plaintext highlighter-rouge">root_info</code> - this is needed to determine the root namespace_id.</p>

<p>Then set the additional header via the built-in middleware stack within the gem:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">client</span> <span class="o">=</span> <span class="no">DropboxApi</span><span class="o">::</span><span class="no">Client</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="s2">"VofXAX8D..."</span><span class="p">)</span>

<span class="n">namespace_id</span> <span class="o">=</span> <span class="n">client</span><span class="p">.</span><span class="nf">get_current_account</span><span class="p">.</span><span class="nf">root_info</span><span class="p">.</span><span class="nf">root_namespace_id</span>

<span class="n">client</span><span class="p">.</span><span class="nf">middleware</span><span class="p">.</span><span class="nf">prepend</span> <span class="k">do</span> <span class="o">|</span><span class="n">connection</span><span class="o">|</span>
  <span class="n">connection</span><span class="p">.</span><span class="nf">headers</span><span class="p">[</span><span class="s1">'Dropbox-API-Path-Root'</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"{</span><span class="se">\"</span><span class="s2">.tag</span><span class="se">\"</span><span class="s2">: </span><span class="se">\"</span><span class="s2">namespace_id</span><span class="se">\"</span><span class="s2">, </span><span class="se">\"</span><span class="s2">namespace_id</span><span class="se">\"</span><span class="s2">: </span><span class="se">\"</span><span class="si">#{</span><span class="n">namespace_id</span><span class="si">}</span><span class="se">\"</span><span class="s2">}"</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Hopefully this will help someone out there.</p>

<blockquote>
  <p>This is dependant on having your <code class="language-plaintext highlighter-rouge">Dropbox App</code> correctly configured with the necessary permissions.</p>
</blockquote>]]></content><author><name></name></author><summary type="html"><![CDATA[I’m using a business dropbox account and have access to the teams shared folders through the various interfaces. Why then when using the API does it only show my personal folder when connecting via the dropbox API?]]></summary></entry><entry><title type="html">Celebrating the little things</title><link href="https://dwhenry.github.io/2020/07/20/celebrating-the-little-things/" rel="alternate" type="text/html" title="Celebrating the little things" /><published>2020-07-20T00:00:00+00:00</published><updated>2020-07-20T00:00:00+00:00</updated><id>https://dwhenry.github.io/2020/07/20/celebrating-the-little-things</id><content type="html" xml:base="https://dwhenry.github.io/2020/07/20/celebrating-the-little-things/"><![CDATA[<p>I remember when Harry was born, he was our first child and we couldn’t have been more proud or happy. He was a normal child for the most part, had trouble latching, but so did our second. We went home the same as most parents, with no idea what we were doing or what to expect.</p>

<p>At first everything was fine, but then he was diagnosed with reflux followed by a milk intolerance, looking back it’s hard to know what was true, the only thing for sure was that the doctors didn’t know. As he grew, so did our expectations, we had made friends at NCT and each parent was taking it in turns to boast about their childs latest achivements, but it never felt like we had much to say. By 8 months it was clear that Harry was behind, at 11 he had his first seizure, by 12 month we had a diagnosis, FoxG1.</p>

<p>And with that our expectations changed, it went from thoughts of taking him to play his first rugby game, to getting excited by each little achiveement he managed. It meant sharing in his joy, be it the first time he managed to put a bell off his round-about in his mouth (this took 4 months of trying), or his belly giggles whenever I take him on a swing.</p>

<p>Harry is not a normal child, but he does have the ability to brighten the lives of everyone that he meets and for that I think he is as special as they come. :)</p>]]></content><author><name></name></author><summary type="html"><![CDATA[I remember when Harry was born, he was our first child and we couldn’t have been more proud or happy. He was a normal child for the most part, had trouble latching, but so did our second. We went home the same as most parents, with no idea what we were doing or what to expect.]]></summary></entry><entry><title type="html">Getting a snapshot downloading from Heroku Elasticsearch</title><link href="https://dwhenry.github.io/2020/06/23/getting-a-snapshot-downloading-from-heroku/" rel="alternate" type="text/html" title="Getting a snapshot downloading from Heroku Elasticsearch" /><published>2020-06-23T00:00:00+00:00</published><updated>2020-06-23T00:00:00+00:00</updated><id>https://dwhenry.github.io/2020/06/23/getting-a-snapshot-downloading-from-heroku</id><content type="html" xml:base="https://dwhenry.github.io/2020/06/23/getting-a-snapshot-downloading-from-heroku/"><![CDATA[<p>Getting a snapshot downloading from Heroku Elasticsearch</p>

<p>There was recently a requirement to get a copy of a Elasticsearch (ES) dump onto our local machines, Due to the amount of data, and the way it was being indexed, it would have taken around 8 days to regenerate the data locally. We could have improved the performance on this by re-writing the indexing process to using the bulk endpoint, however as this code was untested, and we were in the process of decommissioning it the download seemed like a better solution.</p>

<p>Heroku is normally pretty good at giving you access to your data (see the Postgres snapshot download facility in the CLI). This is not the case of the ES data, you do have the facility to create snapshots, but these are stored on a S3 cluster that you can’t get access to, you can restore the snapshots, but only to other Heroku instances, so how do you go about getting a download?</p>

<p>The solution it turns out is pretty easy, you need to setup an additional snapshot repository, pointed at a s3 bucket that you have permission to download from. The below outlines the steps to get this setup and then downloaded.</p>

<h2 id="1-get-access-to-the-elastcisearch-instance">1. Get access to the Elastcisearch instance.</h2>

<p>This is actually pretty easy. Log into Heroku, access the ES Addon and under ‘Application’ there is a copy endpoint button.</p>

<p><img src="https://64.media.tumblr.com/22e74af98dc273e322e8741c9161f655/f61585faf7d23b27-71/s500x750/05ad0f1c34a684525f17f17e26a08335ffdaa0e9.png" alt="image" /></p>

<p>This only gives you the URL and port, which isn’t super useful as it is password protected. I was able to retrieve the username (default elastic username) and password from the Heroku settings, but this may vary depending on your setup. Alternatively you can reset the password, be careful if you are doing this on a production system as you easy lock you main application out.</p>

<h2 id="2-get-a-storage-location">2. Get a storage location</h2>

<p>As I mention at the top of this article we will be using s3 to store the snapshot, so you will need a bucket in the desired region (we will talk more about regions below), and I would suggest it should be password protected.</p>

<p>Once you have these details you are good to proceed to connecting the repository</p>

<h2 id="3-setting-up-the-repository">3. Setting up the repository</h2>

<p>This can be done using a PUT request to the ES system</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>URL:
&lt;es_host&gt;/_snapshot/&lt;repository_name&gt;?verify=false&amp;pretty

BODY:
{
  "type": "s3",
  "settings": {
    "bucket": &lt;bucket_name&gt;,
    "region": &lt;region&gt;,
    "access_key": &lt;access_key&gt;,
    "secret_key": &lt;secret_key&gt;,
    "compress": "true",
    "base_path": "es_snapshots/"
  }
}
</code></pre></div></div>

<p>This works great is your bucket is in <code class="language-plaintext highlighter-rouge">us-east-1</code> which is teh default, but what is you need to store the data in a different region? While you can set the region in the above PUT, you can only set the AWS URL in the client settings in the <code class="language-plaintext highlighter-rouge">elastcisearch.yaml</code> file on the server - that we don’t have access to and can’t change. Reading the docs shows that this is actually set at a <a href="https://www.elastic.co/guide/en/elasticsearch/plugins/current/repository-s3-client.html">client level</a>, and due to data laws if you are running in you ES cluster in Europe then any backups must also be stored in Europe, so it must be possible to connect to a eu-west-1 bucket. But how?</p>

<p>I am using a tool call <code class="language-plaintext highlighter-rouge">Elasticvue</code> to manage my ES indicies, using this I can connect to the ES host using the details we got above. In the repository screen I could see the existing repository and but hovering over the settings I was able to get the client they are using for <code class="language-plaintext highlighter-rouge">eu-west-1</code>.</p>

<p>So updating the body of the above PUT request to also set the client:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    "client:" &lt;client_id&gt;
</code></pre></div></div>

<p>I can now connect to <code class="language-plaintext highlighter-rouge">eu-west-1</code> buckets.</p>

<h2 id="4-creating-the-snapshot">4. Creating the snapshot</h2>

<p>I used Elasticvue to do this as well, it was simply a matter of clicking ‘create snapshot’ for the new repository and then waiting for it to finish - about 10min.</p>

<h2 id="5-restoring-the-snapshot">5. Restoring the snapshot</h2>

<p>You could do this by connecting to the same s3 bucket with your local ES instance and then doing a snapshot restore, however I never managed to get this to work, and it actually turned out to be much easier to use the AWS CLI to download the folder locally and then do a snapshot restore</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aws s3 sync s3://&lt;bucket_name&gt;/ &lt;local_path&gt;
</code></pre></div></div>

<p>And that is it you should now have a copy of you ES indicies on your local machine. &lt;/local_path&gt;&lt;/bucket_name&gt;&lt;/client_id&gt;&lt;/secret_key&gt;&lt;/access_key&gt;&lt;/bucket_name&gt;&lt;/repository_name&gt;&lt;/es_host&gt;</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Getting a snapshot downloading from Heroku Elasticsearch]]></summary></entry><entry><title type="html">Optimising for trouble</title><link href="https://dwhenry.github.io/2020/05/15/optimising-for-trouble/" rel="alternate" type="text/html" title="Optimising for trouble" /><published>2020-05-15T00:00:00+00:00</published><updated>2020-05-15T00:00:00+00:00</updated><id>https://dwhenry.github.io/2020/05/15/optimising-for-trouble</id><content type="html" xml:base="https://dwhenry.github.io/2020/05/15/optimising-for-trouble/"><![CDATA[<p>Building an application to run on the heroku free tier is an exercise in optimisations. You need to consider your process counts, do you need a background worker, what about database rows or even redis memory. These are all things that is you aren’t cardefully will bring down you application. Now don’t get me wrong I’m happy to pay to a heroku worker if it is required, but I mostly definitely don’t want to pay for 3, plus a database if I don’t have to.</p>

<p>I recently been building a card game, and my first thjought was that carding each card individually would quickly exceed the DB row limit (currently 10,000) and bring down the application. To avoid this I stored the cards as a JSON blob on a single game object. This approach is measn that I would need to deserailise the game state, update and the serialize it again for every update, this might(?) be fine if I only have a single playing in the game, for a multiplayer game this would quickly become a bottleneck and result in slow response times</p>

<h2 id="the-initial-solution">The initial solution</h2>

<p>Storing card ownership in a redis DB, this would be super quick way to manage card locking adn avoid hitting the DB.</p>

<p>This approach has a number of issues:</p>

<ul>
  <li>Redis locking is a pain</li>
  <li>ID selection is difficult, especially when you don’t have a card ID fro the front-end, but instead just a location key.</li>
  <li>How do you release a locks? what happens if the user refreshes while holding a card?</li>
  <li>How do you avoid sync issues between redis and the postgres DB</li>
  <li>I would need a background worker to actually perform events later and sync the redis data back into postgres</li>
</ul>

<p>Not only that, it was hard to reason about, difficult to tests for and would add a large amount of complexity to the core of the backend, something that is never a good thing at the start of a project.</p>

<h2 id="the-easy-solution">The easy solution</h2>

<p>While thinking over the problems I was having with the redis solution I satrted to think about why I wasn’t just using the postgres DB to do my locking. After all postgres is pretty good at locking, it has a number of feature to deal with different lockjing scenarios. In fact the only reason I wasn’t using it was the row limit, so I has a look at my current usage, 10 rows…</p>

<p>If I assumed each game would need around 100 cards, then I could have 100 games before I actually hit the row limit. It was at this point I realised I has made a mistake. I was optimising against an issue that didn’t currently exist, and if I was careful with my implementation it would potentially be thousands of games before it was an issues.</p>

<p>With that in mind I update the application to store each card in the DB, this means that eadch player can update game cards simultaneously. It: * Solved the locking problems as I could just releasing any card I currently holding - with a simple update query - before I selected a new card. * Was easy to reason about and test, which was a big win over the redis solution. * allowed updated in request instaed of needing a back ground wokrer, as it only has to update simple card objects instead of a shared global state.</p>

<p>I still have the card state on the game object, this allows games which have been completed (or where no-one has made a move in a while) to be archived. These chnages mean I would require around 100 simultaneous games in process before I reached the DB limit.</p>

<h2 id="conclusions">Conclusions</h2>

<p>I guess the key point here is that optimising is great, but if it isn’t a requirement right now, then you are most likely just making your code more complex without any gain, and in some cases you could even be dooming your project to fail. This is an easy pitfall to run into and can be hard to recognise from the inside, but once you do it is often clear where you went wrong. The best way to guard against this to watch out of smells in your code, as good design will (as a general rule) lead to simple code that is easy to understand.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Building an application to run on the heroku free tier is an exercise in optimisations. You need to consider your process counts, do you need a background worker, what about database rows or even redis memory. These are all things that is you aren’t cardefully will bring down you application. Now don’t get me wrong I’m happy to pay to a heroku worker if it is required, but I mostly definitely don’t want to pay for 3, plus a database if I don’t have to.]]></summary></entry><entry><title type="html">Why was that so hard to find</title><link href="https://dwhenry.github.io/2020/05/15/why-was-that-so-hard-to-find/" rel="alternate" type="text/html" title="Why was that so hard to find" /><published>2020-05-15T00:00:00+00:00</published><updated>2020-05-15T00:00:00+00:00</updated><id>https://dwhenry.github.io/2020/05/15/why-was-that-so-hard-to-find</id><content type="html" xml:base="https://dwhenry.github.io/2020/05/15/why-was-that-so-hard-to-find/"><![CDATA[<p>In the interest of learning something new and filling in my time during the lockdown, I have been building my first react application. I recently been moving to use hooks and adding test coverage. For the most part I have found it easy to write the code, maintain sensible object boundries, and find help online when needed. However the documentation for to update data from external processes was unclear and for the most part non-existant.</p>

<p>I’m not sure if this is due to me being new to React and everyone just knows how to do this, or (and this seems unlikley) the only people who write react blogs are actually those who are building a tasks list application and so don’t need this functionality. What I will say is that once you understand how to use the hooks to pull data in it is actually extremely easy, which again makes me ask why this isn’t covered more widely.</p>

<p>So how did I do it:</p>

<h3 id="create-a-component-to-track-interest-in-updates-and-poll-the-backend-for-updates">Create a component to track interest in updates and poll the backend for updates:</h3>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">cardsByStack</span> <span class="o">=</span> <span class="p">{};</span>
<span class="kd">let</span> <span class="nx">watchers</span> <span class="o">=</span> <span class="p">{};</span>

<span class="c1">// allow registering to receive updates</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">watch</span> <span class="o">=</span> <span class="p">(</span><span class="nx">stackId</span><span class="p">,</span> <span class="nx">watchMethod</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">watchers</span><span class="p">[</span><span class="nx">stackId</span><span class="p">]</span> <span class="o">=</span> <span class="nx">watchers</span><span class="p">[</span><span class="nx">stackId</span><span class="p">]</span> <span class="o">||</span> <span class="p">[];</span>
  <span class="nx">watchers</span><span class="p">[</span><span class="nx">stackId</span><span class="p">].</span><span class="nf">push</span><span class="p">(</span><span class="nx">watchMethod</span><span class="p">);</span>

  <span class="nf">watchMethod</span><span class="p">(</span><span class="nx">cardsByStack</span><span class="p">[</span><span class="nx">stackId</span><span class="p">]);</span>
<span class="p">}</span>

<span class="c1">// allow deregistering to receive updates</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">unWatch</span> <span class="o">=</span> <span class="p">(</span><span class="nx">stackId</span><span class="p">,</span> <span class="nx">watchMethod</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">watchers</span><span class="p">[</span><span class="nx">stackId</span><span class="p">]</span> <span class="o">=</span> <span class="nx">watchers</span><span class="p">[</span><span class="nx">stackId</span><span class="p">].</span><span class="nf">filter</span><span class="p">(</span><span class="nx">watcher</span> <span class="o">=&gt;</span> <span class="nx">watcher</span> <span class="o">!==</span> <span class="nx">watchMethod</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">// do an update</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">pollForAddedCards</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">let</span> <span class="nx">stack_id</span><span class="p">,</span> <span class="nx">cards</span> <span class="o">=</span> <span class="p">...</span>

  <span class="nx">cardsByStack</span><span class="p">[</span><span class="nx">stack_id</span><span class="p">].</span><span class="nf">push</span><span class="p">(</span><span class="nx">card</span><span class="p">);</span>

  <span class="nx">watchers</span><span class="p">[</span><span class="nx">stackId</span><span class="p">].</span><span class="nf">forEach</span><span class="p">(</span><span class="nx">watchMethod</span> <span class="o">=&gt;</span> <span class="nf">watchMethod</span><span class="p">(</span><span class="nx">cards</span><span class="p">[</span><span class="nx">stackId</span><span class="p">]));</span>
<span class="p">}</span>

<span class="nf">setInterval</span><span class="p">(</span><span class="nx">pollForAddedCards</span><span class="p">,</span> <span class="mi">1000</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="registor-interest-in-update-with-the-ability-to-update-the-local-state">Registor interest in update, with the ability to update the local state:</h3>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">import</span> <span class="n">React</span><span class="p">,</span> <span class="p">{</span> <span class="n">useState</span><span class="p">,</span> <span class="n">useEffect</span> <span class="p">}</span> <span class="k">from</span> <span class="nv">"react"</span>
<span class="n">import</span> <span class="p">{</span><span class="n">addEvent</span><span class="p">,</span> <span class="n">updateCard</span><span class="p">,</span> <span class="n">watch</span><span class="p">,</span> <span class="n">unWatch</span><span class="p">}</span> <span class="k">from</span> <span class="s1">'../state/CardState'</span><span class="p">;</span>

<span class="n">const</span> <span class="n">CardStack</span> <span class="o">=</span> <span class="p">(</span><span class="n">props</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="n">const</span> <span class="p">[</span><span class="n">cards</span><span class="p">,</span> <span class="n">setCards</span><span class="p">]</span> <span class="o">=</span> <span class="n">useState</span><span class="p">();</span>

  <span class="p">....</span>

  <span class="n">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="n">let</span> <span class="n">watchCallback</span> <span class="o">=</span> <span class="p">(</span><span class="n">cards</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="n">setCards</span><span class="p">(</span><span class="n">cards</span><span class="p">)</span> <span class="p">}</span>

    <span class="n">watch</span><span class="p">(</span><span class="n">props</span><span class="p">.</span><span class="n">stackId</span><span class="p">,</span> <span class="n">watchCallback</span><span class="p">)</span>
    <span class="k">return</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="n">unWatch</span><span class="p">(</span><span class="n">props</span><span class="p">.</span><span class="n">stackId</span><span class="p">,</span> <span class="n">watchCallback</span><span class="p">)</span> <span class="p">}</span>
  <span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>

<p>So how does this all work….</p>

<p><code class="language-plaintext highlighter-rouge">useEffect</code> is the important thing here. Calling it without a second property means the code inside is run when the component loads/unloads, this allows me to pass the state modifier funtion <code class="language-plaintext highlighter-rouge">setCard</code> into the non React view hierarchy part of my application (called CardState here), where it can be called (and trigger view updates) asyncronously. Once you have understoof this, it is quite easy to receive updates from external sources.</p>

<p><strong>WARNING:</strong> This is demonstrating how you trigger render updates from external parts of your code - i.e. using setTimeout to poll the server for updates from other players - with just react hooks. This is done by using module variable to store what is essentially global state, and while this works I would recommend against it for anything that have more than 1 global state. You are instead better off using one of the existing libraries like: * redux * reactn * recoil</p>]]></content><author><name></name></author><summary type="html"><![CDATA[In the interest of learning something new and filling in my time during the lockdown, I have been building my first react application. I recently been moving to use hooks and adding test coverage. For the most part I have found it easy to write the code, maintain sensible object boundries, and find help online when needed. However the documentation for to update data from external processes was unclear and for the most part non-existant.]]></summary></entry><entry><title type="html">Order of magnitude</title><link href="https://dwhenry.github.io/2018/12/17/order-of-magnitude/" rel="alternate" type="text/html" title="Order of magnitude" /><published>2018-12-17T00:00:00+00:00</published><updated>2018-12-17T00:00:00+00:00</updated><id>https://dwhenry.github.io/2018/12/17/order-of-magnitude</id><content type="html" xml:base="https://dwhenry.github.io/2018/12/17/order-of-magnitude/"><![CDATA[<p>I enjoy doing online puzzles and feel one of the best types of puzzles that I encounter are ones where the solution requires you to rethink your approach as the problem sets increases. The below shows two solutions to a given problem (neither of which are particularly pretty as they are written for the sole purpose of calculating a one off solution) - see the problem <a href="https://adventofcode.com/2018/day/9">here</a>.</p>

<h2 id="solution-1">Solution 1</h2>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="n">pieces</span> <span class="o">=</span> <span class="mi">70904</span> <span class="o">*</span> <span class="mi">100</span>
    <span class="n">players</span> <span class="o">=</span> <span class="mi">473</span>

    <span class="n">played_pieces</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">]</span>
    <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span>
    <span class="n">player</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span>
    <span class="n">result</span> <span class="o">=</span> <span class="no">Hash</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span><span class="o">|</span><span class="n">h</span><span class="p">,</span><span class="n">k</span><span class="o">|</span> <span class="n">h</span><span class="p">[</span><span class="n">k</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">}</span>

    <span class="p">(</span><span class="n">pieces</span> <span class="o">+</span> <span class="mi">1</span><span class="p">).</span><span class="nf">times</span> <span class="k">do</span> <span class="o">|</span><span class="n">piece</span><span class="o">|</span>
      <span class="n">player</span> <span class="o">=</span> <span class="n">player</span> <span class="o">&gt;=</span> <span class="n">players</span> <span class="p">?</span> <span class="mi">1</span> <span class="p">:</span> <span class="n">player</span> <span class="o">+</span> <span class="mi">1</span>
      <span class="k">next</span> <span class="k">if</span> <span class="n">piece</span> <span class="o">&lt;</span> <span class="mi">2</span>

      <span class="k">if</span> <span class="n">piece</span> <span class="o">%</span> <span class="mi">23</span> <span class="o">!=</span> <span class="mi">0</span>
        <span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span>
        <span class="n">i</span> <span class="o">-=</span> <span class="n">played_pieces</span><span class="p">.</span><span class="nf">size</span> <span class="k">if</span> <span class="n">i</span> <span class="o">&gt;=</span> <span class="n">played_pieces</span><span class="p">.</span><span class="nf">size</span>
        <span class="n">played_pieces</span> <span class="o">=</span> <span class="n">played_pieces</span><span class="p">[</span><span class="mi">0</span><span class="o">..</span><span class="n">i</span><span class="p">]</span> <span class="o">+</span> <span class="p">[</span><span class="n">piece</span><span class="p">]</span> <span class="o">+</span> <span class="n">played_pieces</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="o">..-</span><span class="mi">1</span><span class="p">]</span>
        <span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span>
      <span class="k">else</span>
        <span class="n">i</span> <span class="o">-=</span> <span class="mi">7</span>
        <span class="n">i</span> <span class="o">+=</span> <span class="n">played_pieces</span><span class="p">.</span><span class="nf">size</span> <span class="k">if</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">0</span>
        <span class="n">result</span><span class="p">[</span><span class="n">player</span><span class="p">]</span> <span class="o">=</span> <span class="n">result</span><span class="p">[</span><span class="n">player</span><span class="p">]</span> <span class="o">+</span> <span class="n">piece</span> <span class="o">+</span> <span class="n">played_pieces</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>

        <span class="k">if</span> <span class="n">i</span> <span class="o">==</span> <span class="mi">0</span>
          <span class="nb">print</span> <span class="s1">'x'</span>
          <span class="n">played_pieces</span> <span class="o">=</span> <span class="n">played_pieces</span><span class="p">[</span><span class="mi">1</span><span class="o">..-</span><span class="mi">1</span><span class="p">]</span>
        <span class="k">else</span>
          <span class="n">played_pieces</span> <span class="o">=</span> <span class="n">played_pieces</span><span class="p">[</span><span class="mi">0</span><span class="o">..</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="n">played_pieces</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="o">..-</span><span class="mi">1</span><span class="p">]</span>
        <span class="k">end</span>
      <span class="k">end</span>
    <span class="k">end</span>

    <span class="nb">puts</span> <span class="n">result</span><span class="p">.</span><span class="nf">values</span><span class="p">.</span><span class="nf">sort</span><span class="p">.</span><span class="nf">last</span>
</code></pre></div></div>

<h2 id="solution-2">Solution 2</h2>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="n">pieces</span> <span class="o">=</span> <span class="mi">70904</span> <span class="o">*</span> <span class="mi">100</span>
    <span class="n">players</span> <span class="o">=</span> <span class="mi">473</span>

    <span class="k">class</span> <span class="nc">Node</span>
      <span class="nb">attr_accessor</span> <span class="ss">:p</span><span class="p">,</span> <span class="ss">:n</span>
      <span class="nb">attr_reader</span> <span class="ss">:value</span>

      <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="nb">p</span><span class="p">,</span> <span class="n">n</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
        <span class="vi">@p</span> <span class="o">=</span> <span class="nb">p</span>
        <span class="vi">@n</span> <span class="o">=</span> <span class="n">n</span>
        <span class="vi">@value</span> <span class="o">=</span> <span class="n">value</span>
      <span class="k">end</span>

      <span class="k">def</span> <span class="nf">prev</span><span class="p">(</span><span class="n">c</span><span class="o">=</span><span class="mi">7</span><span class="p">)</span>
        <span class="n">c</span> <span class="o">==</span> <span class="mi">1</span> <span class="p">?</span> <span class="nb">p</span> <span class="p">:</span> <span class="nb">p</span><span class="p">.</span><span class="nf">prev</span><span class="p">(</span><span class="n">c</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span>
      <span class="k">end</span>

      <span class="k">def</span> <span class="nf">insert</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
        <span class="nb">self</span><span class="p">.</span><span class="nf">n</span><span class="p">.</span><span class="nf">p</span> <span class="o">=</span> <span class="nb">self</span><span class="p">.</span><span class="nf">n</span> <span class="o">=</span> <span class="no">Node</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="nb">self</span><span class="p">,</span> <span class="n">n</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
      <span class="k">end</span>

      <span class="k">def</span> <span class="nf">remove</span>
        <span class="nb">p</span><span class="p">.</span><span class="nf">n</span> <span class="o">=</span> <span class="n">n</span>
        <span class="n">n</span><span class="p">.</span><span class="nf">p</span> <span class="o">=</span> <span class="nb">p</span>
        <span class="p">[</span><span class="nb">self</span><span class="p">.</span><span class="nf">n</span><span class="p">,</span> <span class="n">value</span><span class="p">]</span>
      <span class="k">end</span>
    <span class="k">end</span>

    <span class="n">n0</span> <span class="o">=</span> <span class="no">Node</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="kp">nil</span><span class="p">,</span> <span class="kp">nil</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
    <span class="n">n1</span> <span class="o">=</span> <span class="no">Node</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">n0</span><span class="p">,</span> <span class="n">n0</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
    <span class="n">n0</span><span class="p">.</span><span class="nf">n</span> <span class="o">=</span> <span class="n">n0</span><span class="p">.</span><span class="nf">p</span> <span class="o">=</span> <span class="n">n1</span>

    <span class="n">c</span> <span class="o">=</span> <span class="n">n1</span>
    <span class="n">player</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span>
    <span class="n">result</span> <span class="o">=</span> <span class="no">Hash</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span><span class="o">|</span><span class="n">h</span><span class="p">,</span><span class="n">k</span><span class="o">|</span> <span class="n">h</span><span class="p">[</span><span class="n">k</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">}</span>

    <span class="p">(</span><span class="n">pieces</span> <span class="o">+</span> <span class="mi">1</span><span class="p">).</span><span class="nf">times</span> <span class="k">do</span> <span class="o">|</span><span class="n">t</span><span class="o">|</span>
      <span class="n">player</span> <span class="o">=</span> <span class="n">player</span> <span class="o">&gt;=</span> <span class="n">players</span> <span class="p">?</span> <span class="mi">1</span> <span class="p">:</span> <span class="n">player</span> <span class="o">+</span> <span class="mi">1</span>
      <span class="k">next</span> <span class="k">if</span> <span class="n">t</span> <span class="o">&lt;</span> <span class="mi">2</span>

      <span class="k">if</span> <span class="n">t</span> <span class="o">%</span> <span class="mi">23</span> <span class="o">!=</span> <span class="mi">0</span>
        <span class="n">c</span> <span class="o">=</span> <span class="n">c</span><span class="p">.</span><span class="nf">n</span><span class="p">.</span><span class="nf">insert</span><span class="p">(</span><span class="n">t</span><span class="p">)</span>
      <span class="k">else</span>
        <span class="n">c</span><span class="p">,</span> <span class="n">remove_value</span> <span class="o">=</span> <span class="n">c</span><span class="p">.</span><span class="nf">prev</span><span class="p">.</span><span class="nf">remove</span>
        <span class="n">result</span><span class="p">[</span><span class="n">player</span><span class="p">]</span> <span class="o">=</span> <span class="n">result</span><span class="p">[</span><span class="n">player</span><span class="p">]</span> <span class="o">+</span> <span class="n">t</span> <span class="o">+</span> <span class="n">remove_value</span>
      <span class="k">end</span>
    <span class="k">end</span>

    <span class="nb">puts</span> <span class="n">r</span><span class="p">.</span><span class="nf">values</span><span class="p">.</span><span class="nf">sort</span><span class="p">.</span><span class="nf">last</span>
</code></pre></div></div>

<p>Why do I have two solution to a single problem? The puzzle had a significant increase in the problem space (factor of 100), this highlighted a significant performance bug in the solution. The performance was so bad that I was above to write and run the second solution before the first had completed. The issue with the first solution was that as the problem space increased it took increasingly longer to find a solution. the.</p>

<p>This was an exercise in fun, but it is wtill possible to learn something from it, as you can easily encounter similar problems in your day job. Your approach does matter, and it should be geared to the solution. By this I mean my initial approach was the best solution to the initial problem as it was quite easy to write and array storage made sense. The second solution using a linked list is more complicated, using a custom data storeage object, and while the code itself is in some ways simplier, the ideas behind it are more coplicated and would have been overengineered for the initial problem.</p>

<p>I wonder what more junior developers would make of this problem and how they would overcome the inherit failure of the simple (Array based storage) solution? For that matter I wonder how most seniors would tackle this problem?</p>]]></content><author><name></name></author><summary type="html"><![CDATA[I enjoy doing online puzzles and feel one of the best types of puzzles that I encounter are ones where the solution requires you to rethink your approach as the problem sets increases. The below shows two solutions to a given problem (neither of which are particularly pretty as they are written for the sole purpose of calculating a one off solution) - see the problem here.]]></summary></entry><entry><title type="html">Talking the talk</title><link href="https://dwhenry.github.io/2018/08/04/talking-the-talk/" rel="alternate" type="text/html" title="Talking the talk" /><published>2018-08-04T00:00:00+00:00</published><updated>2018-08-04T00:00:00+00:00</updated><id>https://dwhenry.github.io/2018/08/04/talking-the-talk</id><content type="html" xml:base="https://dwhenry.github.io/2018/08/04/talking-the-talk/"><![CDATA[<p>As a contractor one of the most important parts of my job is communication, be it with managers, other developers or members of cross functional teams (I wanted a third thing). While I’ll freely admit that I’m not always the best communicator, it has improved over time and I can happy converse with most levels of the business.</p>

<p>One of the biggest mistaskes that you can make when doing this is to start a conversation using the wrong level of detail and forgetting to give the audiance context before they start. using the correct laguage is important as talking about low level implementation details is unlikely to help you project manager understand the issue.</p>

<p>However starting a conversation without first setting the context will only ever will only result in a miscommunication, no matter how good you explain the problem.</p>

<p>For example if I start by saying something like</p>

<blockquote>
  <p>Editing the hosts file make it impossible to retrieve the real data to cache so instead we are going to use proxy domain names</p>
</blockquote>

<p>It is unlikely anyone will understand why this is important, so what is the solution to this?</p>

<p>For me it is laying out some background to the problem first, basically just explaining what issue I have encountered and why it matters to me:</p>

<blockquote>
  <p>I want to remove external dependancies from the cucumber tests</p>
</blockquote>

<p>I can then start talking about my requirments and what I plan to do, this should start with high levels informatioon, going into detail if appropriate.</p>

<blockquote>
  <p>We want this to automatically cache the response, as a results we have the following approaches…</p>
</blockquote>

<p>This ensures whoever I am conversing with understands why we are having this conversation before I start giving details or asking questions.</p>

<p>As I said at the start I don’t always communicate successfully, but I find tools like this ensure I succeed more regularly that I did previously.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[As a contractor one of the most important parts of my job is communication, be it with managers, other developers or members of cross functional teams (I wanted a third thing). While I’ll freely admit that I’m not always the best communicator, it has improved over time and I can happy converse with most levels of the business.]]></summary></entry><entry><title type="html">Home Again</title><link href="https://dwhenry.github.io/2017/10/26/home-again/" rel="alternate" type="text/html" title="Home Again" /><published>2017-10-26T00:00:00+00:00</published><updated>2017-10-26T00:00:00+00:00</updated><id>https://dwhenry.github.io/2017/10/26/home-again</id><content type="html" xml:base="https://dwhenry.github.io/2017/10/26/home-again/"><![CDATA[<p>My current client is happy enough for me to work from home 2 days a week. This is something I requested so that I could keep my sanity as the 3 hours commute, with a small baby at home, that doesn’t understand nights are for sleeping, was too much.</p>

<p>I’m hopeful, that I can do this more and more on future jobs as working from home can be a real joy, and anyone who has the opportunity should definitely take it.</p>

<p>That is not to say it is all rainbows. As with any perk, it is important that you don’t abuse it or it may just be taken away, so here are my recommendations for anyone looking to WFH.</p>

<h2 id="dont-forget-to-socialize">Don’t’ forget to socialize</h2>

<p>We all do this at work, be it at the coffee machine, over lunch or just with the person next to you. This is an important part of your day as it helps you relax before continuing with your work. As such make sure you have time for your family during the day, as (most likely) they are happy to have you home to talk to as well.</p>

<p>Just be sure that you also ensure your family know that you are home to work or perform odd jobs aroud the house.</p>

<h2 id="have-a-work-plan">Have a work plan</h2>

<p>It is never wise to start work without having a plan for the day. This is doubly true when you are WFH as you can’t just turn to your boss/colleagues to discuss what is next. This is becoming less of a problems as most teams have some form of task online board and you can just pick up the next task.</p>

<p>It is still a good idea to have agreed the next day’s work before you leave the office, especially if you are likely to be working away for multiple days or about to start/finish a major piece of work.</p>

<h2 id="keep-in-touch">Keep in touch</h2>

<p>If your teams are used to remote work, or even if they aren’t, there is a good chance you will use some form of chat to communicate within the team. This is especially important when you are working from home as it the easiest form of communication for you and the team, even if it is just letting everyone know that you have gone to lunch.</p>

<h2 id="be-productive">Be productive</h2>

<p>This is an important one.</p>

<p>Keep in mind that not every day can (or even has to) be super productive, some days are just slow. However, it is important to remember you are being employed to do a job and if your boss/co-workers lose faith in you performing that job while WFH, you will soon find that WFH days are a thing of the past.</p>

<h2 id="not-every-day-can-be-super-productive">Not every day can be super productive</h2>

<p>Sure I already mentioned this in the last point. But I want to raise it again, as just because you are working towards your goals, doesn’t mean you will be super productive. This is fine, you just need to be clearly communicating any blockers and ensuring you ask for help when you can.</p>

<p>It is also important to make yourself available if other in the team have questions for you.</p>

<h2 id="your-not-alone">Your not alone</h2>

<p>Well maybe you are, but you can still use any number of tools to work collaboratory with your colleagues in the office (or even their house if they are also WFH). While it may take some getting used to pairing remotely is definitely possible, some people even prefer it.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[My current client is happy enough for me to work from home 2 days a week. This is something I requested so that I could keep my sanity as the 3 hours commute, with a small baby at home, that doesn’t understand nights are for sleeping, was too much.]]></summary></entry></feed>