<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
  <atom:link href="https://www.mikenotthepope.com/feed.xml" rel="self" type="application/rss+xml" />
  <title>blog</title>
  <link>https://www.mikenotthepope.com</link>
  <description>My beautiful website</description>
  <language>en-us</language>
  <generator>Tableau v0.27.0</generator>
    <item>
       <title>An LLM Is (Not Really) A Black Box Full Of Sudoku And Tic Tac Toe Games</title>
       <link>https://www.mikenotthepope.com/an-llm-is-not-really-a-black-box-full-of-sudoku-and-tic-tac-toe-games/</link>
       <pubDate>Thu, 23 Oct 2025 01:00:00 -04</pubDate>
       <guid>https://www.mikenotthepope.com/an-llm-is-not-really-a-black-box-full-of-sudoku-and-tic-tac-toe-games/</guid>
       <description><![CDATA[ <p>This morning I was playing with <a href="https://en.wikipedia.org/wiki/Ollama">Ollama</a> running the <a href="https://ollama.com/library/qwen2.5-coder:7b">qwen2.5-coder:7b</a> model because I wanted to see what running a <a href="https://en.wikipedia.org/wiki/Large_language_model">large language model</a> on my MacBook is like. I asked it "Can you generate an image?" to which it replied "dude, I can only spit out text, I'm not <a href="https://www.midjourney.com">Midjourney</a>."</p>
<pre class="athl" style="color: #e0e2ea; background-color: #14161b;"><code class="language-plaintext" translate="no" tabindex="0"><div class="line" data-line="1">&gt;&gt;&gt; Create an image of a dog peeing on a hydrant
</div><div class="line" data-line="2">I&#39;m sorry, but I can&#39;t assist with that request.
</div><div class="line" data-line="3">
</div><div class="line" data-line="4">&gt;&gt;&gt; The image or the dog doing his business?
</div><div class="line" data-line="5">Both.  Perhaps you should use a different model?  I want a different user.
</div></code></pre>
<p>It was at this point I realized I have no idea what Ollama does or what a model is. The short version on Ollama is that it is a free program that lets you download and run AI chatbots directly on your own computer instead of using them through a website. But this article is about LLMs, so...</p>
<h2><a href="#whats-actually-inside-that-qwen25-coder7b-model-file-i-just-downloaded" aria-hidden="true" class="anchor" id="whats-actually-inside-that-qwen25-coder7b-model-file-i-just-downloaded"></a>What's Actually Inside that qwen2.5-coder:7b Model File I Just Downloaded?</h2>
<p>It turns out a model is basically a huge collection of grids filled with numbers called matrices (matrix is the singular).  My brain naturally thinks it's basically a multigigabyte file full of Sudoku puzzles and Tic Tac Toe games, and this is where being self-taught starts to show. Any computer science student would recognize these matrices from their linear algebra class, which is required for most computer science degrees. Since I've never taken linear algebra, I'm figuring this out backwards, but at least now I know what I actually downloaded.  So...</p>
<h2><a href="#what-is-linear-algebra" aria-hidden="true" class="anchor" id="what-is-linear-algebra"></a>What Is Linear Algebra?</h2>
<p>Linear algebra is math that helps you work with lists of numbers and grids of numbers at the same time. It's kind of like organizing toys into boxes and then figuring out how to rearrange all the boxes together in smart ways. It's super useful for teaching computers how to recognize pictures, make video game graphics, and lots of other cool stuff. OK, if an LLM is just a bunch of matrices, then...</p>
<h2><a href="#how-do-these-sudoku-puzzles-turn-into-chatgpt" aria-hidden="true" class="anchor" id="how-do-these-sudoku-puzzles-turn-into-chatgpt"></a>How Do These "Sudoku Puzzles" Turn into ChatGPT?</h2>
<p>I've learned that when I type something into ChatGPT, my words get turned into numbers. Then those numbers get multiplied through all those matrix grids, over and over and over, maybe hundreds of times.  After bouncing through all these grids, the numbers somehow spit out a prediction for what word should come next. Then that word goes back in, runs through the grids again, and predicts the next word. Rinse and repeat until you've got a sentence.  It's like a crazy complicated pinball machine made of math. Words go in, bounce around through a ton of number grids, and somehow actual answers come out the other side.</p>
<h2><a href="#why-do-i-need-to-know-any-of-this" aria-hidden="true" class="anchor" id="why-do-i-need-to-know-any-of-this"></a>Why Do I Need to Know Any of This?</h2>
<p>I'm doing some web development, and it's helpful to at least understand how all the pieces fit together.  So now I've learned an LLM is text in -> math stuff -> text out.  That's good enough for now.  If I want to generate images, which I don't at the moment, I'll look into that later.  But this helps me understand a whole lot better that <a href="https://chatgpt.com">ChatGPT.com</a> is a website, <a href="https://en.wikipedia.org/wiki/GPT-5">GPT-5</a> is a large language model, and saying "Ask ChatGPT <code>&lt;some question&gt;</code>" is really saying "Ask the ChatGPT website to ask the GPT-5 model <code>&lt;some question&gt;</code> and let me know what it said".</p> ]]></description>
    </item>
    <item>
       <title>7 Things I Learned Building a Rate-Limited MCP Server in Elixir</title>
       <link>https://www.mikenotthepope.com/7-things-i-learned-building-a-rate-limited-mcp-server-in-elixir/</link>
       <pubDate>Tue, 21 Oct 2025 01:00:00 -04</pubDate>
       <guid>https://www.mikenotthepope.com/7-things-i-learned-building-a-rate-limited-mcp-server-in-elixir/</guid>
       <description><![CDATA[ <p>As part of learning about how AI coding tools work, I keep hearing about <a href="https://en.wikipedia.org/wiki/Model_Context_Protocol">MCP</a> servers.  I was curious what they do, so I built one.  This post is about 7 things I learned.</p>
<h2><a href="#1-an-mcp-server-is-just-a-regular-program" aria-hidden="true" class="anchor" id="1-an-mcp-server-is-just-a-regular-program"></a>1. An MCP Server Is Just A Regular Program</h2>
<p>Once you get behind the fancy jargon, an MCP server is just a regular program.  It receives requests and serves responses.  Sure, you have to learn how to follow the MCP conventions, but when you learn that, there's nothing magical about it.  Just like HTTP servers process HTTP requests, MCP servers process MCP requests.</p>
<h2><a href="#2--mcp-is-a-standardized-way-for-giving-your-ai-chat-thingy-eg-claude-code-tools-it-didnt-come-with" aria-hidden="true" class="anchor" id="2--mcp-is-a-standardized-way-for-giving-your-ai-chat-thingy-eg-claude-code-tools-it-didnt-come-with"></a>2.  MCP Is A Standardized Way For Giving Your AI Chat Thingy (e.g. Claude Code) Tools It Didn't Come With</h2>
<p>Want ChatGPT to search your database? MCP Server. Want Claude Code to access your GitHub repos, run terminal commands, or read your local files? MCP Server. Want your AI assistant to pull data from Slack, Notion, or your company's internal APIs? MCP Server.</p>
<h2><a href="#3-your-ai-tool-already-has-or-soon-will-have-an-mcp-client-built-in" aria-hidden="true" class="anchor" id="3-your-ai-tool-already-has-or-soon-will-have-an-mcp-client-built-in"></a>3. Your AI Tool Already Has (Or Soon Will Have) An MCP Client Built In</h2>
<p>ChatGPT, Claude Code, and other AI assistants come with an MCP client baked in. You don't need to install anything extra on the AI side—it already knows how to talk to MCP servers. You just point it at your MCP server, and it handles the rest.</p>
<h2><a href="#4-the-client-and-server-talk-using-json-rpc-over-standard-io-or-http" aria-hidden="true" class="anchor" id="4-the-client-and-server-talk-using-json-rpc-over-standard-io-or-http"></a>4. The Client and Server Talk Using JSON-RPC Over Standard I/O or HTTP</h2>
<p>MCP uses JSON-RPC for communication—just structured JSON messages back and forth. The connection itself is typically either standard input/output (stdin/stdout) for local processes or HTTP for remote servers.</p>
<h2><a href="#5-your-mcp-server-advertises-its-tools-on-startup" aria-hidden="true" class="anchor" id="5-your-mcp-server-advertises-its-tools-on-startup"></a>5. Your MCP Server Advertises Its Tools On Startup</h2>
<p>When the client connects, the MCP server sends over a list of tools it offers—their names, descriptions, and what parameters they expect. The AI now knows what capabilities it has available and can call those tools as needed during your conversation.</p>
<h2><a href="#6-the-request-flow-is-simple-ask-call-return-answer" aria-hidden="true" class="anchor" id="6-the-request-flow-is-simple-ask-call-return-answer"></a>6. The Request Flow Is Simple: Ask, Call, Return, Answer</h2>
<p>Think of it like a sequence diagram with four actors:</p>
<p><strong>On startup:</strong></p>
<ul>
<li>MCP Host → MCP Server: "What tools do you have?"</li>
<li>MCP Server → MCP Host: "I have get_records, do_thing, etc."</li>
</ul>
<p><strong>When you ask a question:</strong></p>
<ul>
<li>You → MCP Host: "What's in my database?"</li>
<li>MCP Host → LLM: "User asked about database. Available tools: get_records..."</li>
<li>LLM → MCP Host: "Call get_records with limit=10"</li>
<li>MCP Host → MCP Server: JSON-RPC request for get_records</li>
<li>MCP Server → MCP Host: Returns the data</li>
<li>MCP Host → LLM: "Here's the data from get_records"</li>
<li>LLM → MCP Host: "Your database contains 100 items..."</li>
<li>MCP Host → You: "Your database contains 100 items..."</li>
</ul>
<p>The LLM decides <em>what</em> to do. The MCP host does the actual calling.</p>
<h2><a href="#7-your-mcp-server-can-have-whatever-middleware-you-want" aria-hidden="true" class="anchor" id="7-your-mcp-server-can-have-whatever-middleware-you-want"></a>7. Your MCP Server Can Have Whatever Middleware You Want</h2>
<p>Since it's just a regular program, you can add anything between the request and your data source. I didn't want to hammer my database every time the AI got curious, so I added a rate limiter (5 requests per 10 seconds) plus a caching layer. The LLM will happily call your tool 50 times in a row if you let it.</p>
<h2><a href="#in-summary" aria-hidden="true" class="anchor" id="in-summary"></a>In Summary</h2>
<p>I created <a href="https://github.com/MikeNotThePope/hit_me_db_one_more_time">Hit Me DB One More Time</a>, an MCP server in Elixir that demonstrates the plugin pipeline pattern. It has a SQLite database with 100 sample records, a <code>get_records</code> tool for pagination, and a middleware pipeline with rate limiting (5 req/10s), caching (30s TTL), and logging. Requests flow through the pipeline where each plugin can pass through, short-circuit, or modify the context. Check it out if you want to see how it all fits together.</p> ]]></description>
    </item>
    <item>
       <title>I found the missing 6GB on my Mac (APFS, recovery partitions, and GB vs GiB)</title>
       <link>https://www.mikenotthepope.com/i-found-the-missing-6gb-on-my-mac-apfs-recovery-partitions-and-gb-vs-gib/</link>
       <pubDate>Sun, 19 Oct 2025 00:00:00 UTC</pubDate>
       <guid>https://www.mikenotthepope.com/i-found-the-missing-6gb-on-my-mac-apfs-recovery-partitions-and-gb-vs-gib/</guid>
       <description><![CDATA[ <p>I asked Claude: "How do I check free disk space on my Mac?"</p>
<p>Claude suggested <code>df -h</code>, so I did:</p>
<pre class="athl" style="color: #e0e2ea; background-color: #14161b;"><code class="language-bash" translate="no" tabindex="0"><div class="line" data-line="1"><span style="color: #8cf8f7;">$</span> <span style="color: #e0e2ea;">df</span> <span style="color: #e0e2ea;">-h</span> <span style="color: #e0e2ea;">/</span>
</div><div class="line" data-line="2"><span style="color: #8cf8f7;">Filesystem</span>        <span style="color: #e0e2ea;">Size</span>    <span style="color: #e0e2ea;">Used</span>   <span style="color: #e0e2ea;">Avail</span> <span style="color: #e0e2ea;">Capacity</span>  <span style="color: #e0e2ea;">Mounted</span> <span style="color: #e0e2ea;">on</span>
</div><div class="line" data-line="3"><span style="color: #8cf8f7;">/dev/disk3s1s1</span>   <span style="color: #e0e2ea;">460Gi</span>    <span style="color: #e0e2ea;">14Gi</span>   <span style="color: #e0e2ea;">194Gi</span>     <span style="color: #e0e2ea;">7%</span>    <span style="color: #e0e2ea;">/</span>
</div></code></pre>
<p>Only 14Gi used? That seemed wrong. I opened Finder, right-clicked on "Macintosh HD," and selected "Get Info":</p>
<pre class="athl" style="color: #e0e2ea; background-color: #14161b;"><code class="language-bash" translate="no" tabindex="0"><div class="line" data-line="1"><span style="color: #8cf8f7;">Capacity:</span>  <span style="color: #e0e2ea;">494.38</span> <span style="color: #e0e2ea;">GB</span>
</div><div class="line" data-line="2"><span style="color: #8cf8f7;">Available:</span> <span style="color: #e0e2ea;">217.26</span> <span style="color: #e0e2ea;">GB</span><span style="color: #e0e2ea;"></span> <span style="color: #e0e2ea;">(</span><span style="color: #8cf8f7;">8.8</span> <span style="color: #e0e2ea;">GB</span> <span style="color: #e0e2ea;">purgeable</span><span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="3"><span style="color: #8cf8f7;">Used:</span>      <span style="color: #e0e2ea;">268,716,216,320</span> <span style="color: #e0e2ea;">bytes</span><span style="color: #e0e2ea;"></span> <span style="color: #e0e2ea;">(</span><span style="color: #8cf8f7;">268.72</span> <span style="color: #e0e2ea;">GB</span> <span style="color: #e0e2ea;">on</span> <span style="color: #e0e2ea;">disk</span><span style="color: #e0e2ea;">)</span>
</div></code></pre>
<p><code>df</code> says 14Gi used. Finder says 268.72 GB used. A 254GB discrepancy.</p>
<p>Something fundamental was different about how these tools measured disk space, so I dug deeper.</p>
<p>Turns out every tool tells you something different, and they're all technically correct.</p>
<h2><a href="#the-three-methods" aria-hidden="true" class="anchor" id="the-three-methods"></a>The Three Methods</h2>
<h3><a href="#finders-get-info" aria-hidden="true" class="anchor" id="finders-get-info"></a>Finder's "Get Info"</h3>
<p>Right-click your drive, select "Get Info." Finder queries filesystem metadata using APIs like <code>statfs()</code> and <code>getattrlist()</code>. Turns out this is the most accurate method.</p>
<h3><a href="#du---disk-usage" aria-hidden="true" class="anchor" id="du---disk-usage"></a><code>du</code> - Disk Usage</h3>
<p><code>du</code> walks the directory tree and sums file sizes. Problem is, it counts hard links multiple times, ignores sparse files, and knows nothing about APFS snapshots.</p>
<h3><a href="#df---disk-free" aria-hidden="true" class="anchor" id="df---disk-free"></a><code>df</code> - Disk Free</h3>
<pre class="athl" style="color: #e0e2ea; background-color: #14161b;"><code class="language-bash" translate="no" tabindex="0"><div class="line" data-line="1"><span style="color: #8cf8f7;">$</span> <span style="color: #e0e2ea;">df</span> <span style="color: #e0e2ea;">-h</span> <span style="color: #e0e2ea;">/</span>
</div><div class="line" data-line="2"><span style="color: #8cf8f7;">Filesystem</span>        <span style="color: #e0e2ea;">Size</span>    <span style="color: #e0e2ea;">Used</span>   <span style="color: #e0e2ea;">Avail</span> <span style="color: #e0e2ea;">Capacity</span>  <span style="color: #e0e2ea;">Mounted</span> <span style="color: #e0e2ea;">on</span>
</div><div class="line" data-line="3"><span style="color: #8cf8f7;">/dev/disk3s1s1</span>   <span style="color: #e0e2ea;">460Gi</span>    <span style="color: #e0e2ea;">14Gi</span>   <span style="color: #e0e2ea;">194Gi</span>     <span style="color: #e0e2ea;">7%</span>    <span style="color: #e0e2ea;">/</span>
</div></code></pre>
<p><code>df</code> queries filesystem metadata directly. Fast and accurate, but here's the thing: it's asking about the <em>system</em> volume (<code>/</code>), not the data volume where your files actually live.</p>
<h2><a href="#apfs-volume-groups" aria-hidden="true" class="anchor" id="apfs-volume-groups"></a>APFS Volume Groups</h2>
<p>Starting with macOS Catalina, Apple split the filesystem into multiple volumes in a volume group:</p>
<pre class="athl" style="color: #e0e2ea; background-color: #14161b;"><code class="language-bash" translate="no" tabindex="0"><div class="line" data-line="1"><span style="color: #8cf8f7;">$</span> <span style="color: #e0e2ea;">df</span> <span style="color: #e0e2ea;">-h</span>
</div><div class="line" data-line="2"><span style="color: #8cf8f7;">Filesystem</span>        <span style="color: #e0e2ea;">Size</span>    <span style="color: #e0e2ea;">Used</span>   <span style="color: #e0e2ea;">Avail</span> <span style="color: #e0e2ea;">Capacity</span>  <span style="color: #e0e2ea;">Mounted</span> <span style="color: #e0e2ea;">on</span>
</div><div class="line" data-line="3"><span style="color: #8cf8f7;">/dev/disk3s1s1</span>   <span style="color: #e0e2ea;">460Gi</span>    <span style="color: #e0e2ea;">14Gi</span>   <span style="color: #e0e2ea;">194Gi</span>     <span style="color: #e0e2ea;">7%</span>    <span style="color: #e0e2ea;">/</span>
</div><div class="line" data-line="4"><span style="color: #8cf8f7;">/dev/disk3s5</span>     <span style="color: #e0e2ea;">460Gi</span>   <span style="color: #e0e2ea;">236Gi</span>   <span style="color: #e0e2ea;">194Gi</span>    <span style="color: #e0e2ea;">55%</span>    <span style="color: #e0e2ea;">/System/Volumes/Data</span>
</div></code></pre>
<p>Two separate volumes sharing the same 460Gi pool. <code>/</code> is the read-only system volume (14Gi), <code>/System/Volumes/Data</code> is where your files actually live (236Gi). Both show the same total size and available space because they're drawing from the same pool.</p>
<h3><a href="#apfs-snapshots" aria-hidden="true" class="anchor" id="apfs-snapshots"></a>APFS Snapshots</h3>
<pre class="athl" style="color: #e0e2ea; background-color: #14161b;"><code class="language-bash" translate="no" tabindex="0"><div class="line" data-line="1"><span style="color: #8cf8f7;">$</span> <span style="color: #e0e2ea;">tmutil</span> <span style="color: #e0e2ea;">listlocalsnapshots</span> <span style="color: #e0e2ea;">/</span>
</div><div class="line" data-line="2"><span style="color: #8cf8f7;">Snapshots</span> <span style="color: #e0e2ea;">for</span> <span style="color: #e0e2ea;">volume</span> <span style="color: #e0e2ea;">group</span> <span style="color: #e0e2ea;">containing</span> <span style="color: #e0e2ea;">disk</span> <span style="color: #e0e2ea;">/:</span>
</div><div class="line" data-line="3"><span style="color: #8cf8f7;">com.apple.os.update-83920663FCCF8FEE21340F708B980423F7822AE195EAD15998C70BD0F7B8AF23</span>
</div><div class="line" data-line="4"><span style="color: #8cf8f7;">com.apple.os.update-E4CA8A16898710DC4B2F9B0D986B279473F24E3772D6AC9DCEE013E6BCAFF5EA70031C2FF529AB09561D8823597D09DD</span>
</div><div class="line" data-line="5"><span style="color: #8cf8f7;">com.apple.os.update-MSUPrepareUpdate</span>
</div></code></pre>
<p>APFS creates snapshots for system updates and Time Machine. These are "purgeable" (macOS will delete them when it needs space), but they're consuming space right now. They're invisible to <code>du</code>, but counted by <code>df</code> and Finder.</p>
<p>Here's the tricky part: snapshots share data blocks with your live files through copy-on-write. When you delete a snapshot, you might not get back all the space you expect because some blocks are still referenced by other snapshots or live files. You can delete local snapshots manually with <code>tmutil deletelocalsnapshots /</code>, but the space freed depends on how much block sharing exists.</p>
<h3><a href="#what-about-hard-links-and-clones" aria-hidden="true" class="anchor" id="what-about-hard-links-and-clones"></a>What About Hard Links and Clones?</h3>
<p>On macOS, <code>du</code> is smart enough to detect hard links and only counts them once. But APFS has more complex space-sharing mechanisms:</p>
<ul>
<li><strong>Cloned files</strong>: APFS can create instant copies that share blocks until modified (copy-on-write)</li>
<li><strong>Snapshots</strong>: Share blocks with live files, making it hard to predict space freed by deletion</li>
<li><strong>Reflinks</strong>: Files sharing the same underlying data blocks</li>
</ul>
<p>These features mean that deleting a file or snapshot might not free as much space as you'd expect, because the blocks may still be referenced elsewhere.</p>
<h2><a href="#why-finder-shows-a-different-picture" aria-hidden="true" class="anchor" id="why-finder-shows-a-different-picture"></a>Why Finder Shows a Different Picture</h2>
<p>Finder uses <code>statfs()</code> and <code>getattrlist()</code> to query filesystem metadata directly. It accounts for snapshots, purgeable space, and APFS volume groups, giving you a user-friendly view of storage.</p>
<p>However, Finder's numbers aren't always perfectly accurate either. It can show misleading figures for available/purgeable space, especially with APFS snapshots and copy-on-write behavior. For precise analysis, you may need to use <code>diskutil apfs list</code> and inspect snapshots directly.</p>
<h2><a href="#getting-the-real-numbers-programmatically" aria-hidden="true" class="anchor" id="getting-the-real-numbers-programmatically"></a>Getting the Real Numbers Programmatically</h2>
<h3><a href="#python-osstatvfs" aria-hidden="true" class="anchor" id="python-osstatvfs"></a>Python: <code>os.statvfs()</code></h3>
<pre class="athl" style="color: #e0e2ea; background-color: #14161b;"><code class="language-python" translate="no" tabindex="0"><div class="line" data-line="1"><span style="color: #e0e2ea; font-weight: bold;">#!/usr/bin/env python3</span>
</div><div class="line" data-line="2"><span style="color: #e0e2ea; font-weight: bold;">import</span> <span style="color: #e0e2ea;">os</span>
</div><div class="line" data-line="3">
</div><div class="line" data-line="4"><span style="color: #e0e2ea; font-weight: bold;">def</span> <span style="color: #8cf8f7;">get_disk_space</span><span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">path</span><span style="color: #e0e2ea;">)</span><span style="color: #e0e2ea;">:</span>
</div><div class="line" data-line="5">    <span style="color: #b3f6c0;">&quot;&quot;&quot;Get disk space statistics for a given path.&quot;&quot;&quot;</span>
</div><div class="line" data-line="6">    <span style="color: #e0e2ea;">stat</span> <span style="color: #e0e2ea;">=</span> <span style="color: #e0e2ea;">os</span><span style="color: #e0e2ea;">.</span><span style="color: #8cf8f7;">statvfs</span><span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">path</span><span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="7">
</div><div class="line" data-line="8">    <span style="color: #9b9ea4;"># Fragment size * total fragments</span>
</div><div class="line" data-line="9">    <span style="color: #e0e2ea;">total</span> <span style="color: #e0e2ea;">=</span> <span style="color: #e0e2ea;">stat</span><span style="color: #e0e2ea;">.</span><span style="color: #e0e2ea;">f_frsize</span> <span style="color: #e0e2ea;">*</span> <span style="color: #e0e2ea;">stat</span><span style="color: #e0e2ea;">.</span><span style="color: #e0e2ea;">f_blocks</span>
</div><div class="line" data-line="10">
</div><div class="line" data-line="11">    <span style="color: #9b9ea4;"># Fragment size * available fragments (for unprivileged users)</span>
</div><div class="line" data-line="12">    <span style="color: #e0e2ea;">available</span> <span style="color: #e0e2ea;">=</span> <span style="color: #e0e2ea;">stat</span><span style="color: #e0e2ea;">.</span><span style="color: #e0e2ea;">f_frsize</span> <span style="color: #e0e2ea;">*</span> <span style="color: #e0e2ea;">stat</span><span style="color: #e0e2ea;">.</span><span style="color: #e0e2ea;">f_bavail</span>
</div><div class="line" data-line="13">
</div><div class="line" data-line="14">    <span style="color: #9b9ea4;"># Fragment size * free fragments (including reserved)</span>
</div><div class="line" data-line="15">    <span style="color: #e0e2ea;">free</span> <span style="color: #e0e2ea;">=</span> <span style="color: #e0e2ea;">stat</span><span style="color: #e0e2ea;">.</span><span style="color: #e0e2ea;">f_frsize</span> <span style="color: #e0e2ea;">*</span> <span style="color: #e0e2ea;">stat</span><span style="color: #e0e2ea;">.</span><span style="color: #e0e2ea;">f_bfree</span>
</div><div class="line" data-line="16">
</div><div class="line" data-line="17">    <span style="color: #e0e2ea;">used</span> <span style="color: #e0e2ea;">=</span> <span style="color: #e0e2ea;">total</span> <span style="color: #e0e2ea;">-</span> <span style="color: #e0e2ea;">free</span>
</div><div class="line" data-line="18">
</div><div class="line" data-line="19">    <span style="color: #e0e2ea; font-weight: bold;">return</span> <span style="color: #e0e2ea;">&lbrace;</span>
</div><div class="line" data-line="20">        <span style="color: #b3f6c0;">&#39;total&#39;</span><span style="color: #e0e2ea;">:</span> <span style="color: #e0e2ea;">total</span><span style="color: #e0e2ea;">,</span>
</div><div class="line" data-line="21">        <span style="color: #b3f6c0;">&#39;used&#39;</span><span style="color: #e0e2ea;">:</span> <span style="color: #e0e2ea;">used</span><span style="color: #e0e2ea;">,</span>
</div><div class="line" data-line="22">        <span style="color: #b3f6c0;">&#39;free&#39;</span><span style="color: #e0e2ea;">:</span> <span style="color: #e0e2ea;">free</span><span style="color: #e0e2ea;">,</span>
</div><div class="line" data-line="23">        <span style="color: #b3f6c0;">&#39;available&#39;</span><span style="color: #e0e2ea;">:</span> <span style="color: #e0e2ea;">available</span><span style="color: #e0e2ea;">,</span>
</div><div class="line" data-line="24">        <span style="color: #b3f6c0;">&#39;percent_used&#39;</span><span style="color: #e0e2ea;">:</span> <span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">used</span> <span style="color: #e0e2ea;">/</span> <span style="color: #e0e2ea;">total</span><span style="color: #e0e2ea;">)</span> <span style="color: #e0e2ea;">*</span> <span style="color: #e0e2ea;">100</span>
</div><div class="line" data-line="25">    <span style="color: #e0e2ea;">&rbrace;</span>
</div><div class="line" data-line="26">
</div><div class="line" data-line="27"><span style="color: #9b9ea4;"># Check both system and data volumes</span>
</div><div class="line" data-line="28"><span style="color: #e0e2ea; font-weight: bold;">for</span> <span style="color: #e0e2ea;">path</span> <span style="color: #e0e2ea; font-weight: bold;">in</span> <span style="color: #e0e2ea;">[</span><span style="color: #b3f6c0;">&#39;/&#39;</span><span style="color: #e0e2ea;">,</span> <span style="color: #b3f6c0;">&#39;/System/Volumes/Data&#39;</span><span style="color: #e0e2ea;">]</span><span style="color: #e0e2ea;">:</span>
</div><div class="line" data-line="29">    <span style="color: #e0e2ea;">stats</span> <span style="color: #e0e2ea;">=</span> <span style="color: #8cf8f7;">get_disk_space</span><span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">path</span><span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="30">    <span style="color: #8cf8f7;">print</span><span style="color: #e0e2ea;">(</span><span style="color: #b3f6c0;">f&quot;<span style="color: #8cf8f7;">\n</span><span style="color: #8cf8f7;">&lbrace;</span><span style="color: #e0e2ea;">path</span><span style="color: #8cf8f7;">&rbrace;</span>:&quot;</span><span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="31">    <span style="color: #8cf8f7;">print</span><span style="color: #e0e2ea;">(</span><span style="color: #b3f6c0;">f&quot;  Total:     <span style="color: #8cf8f7;">&lbrace;</span><span style="color: #e0e2ea;">stats</span><span style="color: #e0e2ea;">[</span><span style="color: #b3f6c0;">&#39;total&#39;</span><span style="color: #e0e2ea;">]</span> <span style="color: #e0e2ea;">/</span> <span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">1024</span><span style="color: #e0e2ea;">**</span><span style="color: #e0e2ea;">3</span><span style="color: #e0e2ea;">)</span><span style="color: #e0e2ea;">:</span>.2f<span style="color: #8cf8f7;">&rbrace;</span> GiB&quot;</span><span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="32">    <span style="color: #8cf8f7;">print</span><span style="color: #e0e2ea;">(</span><span style="color: #b3f6c0;">f&quot;  Used:      <span style="color: #8cf8f7;">&lbrace;</span><span style="color: #e0e2ea;">stats</span><span style="color: #e0e2ea;">[</span><span style="color: #b3f6c0;">&#39;used&#39;</span><span style="color: #e0e2ea;">]</span> <span style="color: #e0e2ea;">/</span> <span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">1024</span><span style="color: #e0e2ea;">**</span><span style="color: #e0e2ea;">3</span><span style="color: #e0e2ea;">)</span><span style="color: #e0e2ea;">:</span>.2f<span style="color: #8cf8f7;">&rbrace;</span> GiB&quot;</span><span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="33">    <span style="color: #8cf8f7;">print</span><span style="color: #e0e2ea;">(</span><span style="color: #b3f6c0;">f&quot;  Available: <span style="color: #8cf8f7;">&lbrace;</span><span style="color: #e0e2ea;">stats</span><span style="color: #e0e2ea;">[</span><span style="color: #b3f6c0;">&#39;available&#39;</span><span style="color: #e0e2ea;">]</span> <span style="color: #e0e2ea;">/</span> <span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">1024</span><span style="color: #e0e2ea;">**</span><span style="color: #e0e2ea;">3</span><span style="color: #e0e2ea;">)</span><span style="color: #e0e2ea;">:</span>.2f<span style="color: #8cf8f7;">&rbrace;</span> GiB&quot;</span><span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="34">    <span style="color: #8cf8f7;">print</span><span style="color: #e0e2ea;">(</span><span style="color: #b3f6c0;">f&quot;  Usage:     <span style="color: #8cf8f7;">&lbrace;</span><span style="color: #e0e2ea;">stats</span><span style="color: #e0e2ea;">[</span><span style="color: #b3f6c0;">&#39;percent_used&#39;</span><span style="color: #e0e2ea;">]</span><span style="color: #e0e2ea;">:</span>.1f<span style="color: #8cf8f7;">&rbrace;</span>%&quot;</span><span style="color: #e0e2ea;">)</span>
</div></code></pre>
<p>Output:</p>
<pre class="athl" style="color: #e0e2ea; background-color: #14161b;"><code class="language-python" translate="no" tabindex="0"><div class="line" data-line="1"><span style="color: #e0e2ea;">/</span><span style="color: #e0e2ea;">:</span>
</div><div class="line" data-line="2">  <span style="color: #e0e2ea;">Total</span><span style="color: #e0e2ea;">:</span>     <span style="color: #e0e2ea;">460.43</span> <span style="color: #e0e2ea;">GiB</span>
</div><div class="line" data-line="3">  <span style="color: #e0e2ea;">Used</span><span style="color: #e0e2ea;">:</span>      <span style="color: #e0e2ea;">266.37</span> <span style="color: #e0e2ea;">GiB</span>
</div><div class="line" data-line="4">  <span style="color: #e0e2ea;">Available</span><span style="color: #e0e2ea;">:</span> <span style="color: #e0e2ea;">194.07</span> <span style="color: #e0e2ea;">GiB</span>
</div><div class="line" data-line="5">  <span style="color: #e0e2ea;">Usage</span><span style="color: #e0e2ea;">:</span>     <span style="color: #e0e2ea;">57.9</span><span style="color: #e0e2ea;">%</span>
</div><div class="line" data-line="6">
</div><div class="line" data-line="7"><span style="color: #e0e2ea;">/</span><span style="color: #e0e2ea;">System</span><span style="color: #e0e2ea;">/</span><span style="color: #e0e2ea;">Volumes</span><span style="color: #e0e2ea;">/</span><span style="color: #e0e2ea;">Data</span><span style="color: #e0e2ea;">:</span>
</div><div class="line" data-line="8">  <span style="color: #e0e2ea;">Total</span><span style="color: #e0e2ea;">:</span>     <span style="color: #e0e2ea;">460.43</span> <span style="color: #e0e2ea;">GiB</span>
</div><div class="line" data-line="9">  <span style="color: #e0e2ea;">Used</span><span style="color: #e0e2ea;">:</span>      <span style="color: #e0e2ea;">266.37</span> <span style="color: #e0e2ea;">GiB</span>
</div><div class="line" data-line="10">  <span style="color: #e0e2ea;">Available</span><span style="color: #e0e2ea;">:</span> <span style="color: #e0e2ea;">194.07</span> <span style="color: #e0e2ea;">GiB</span>
</div><div class="line" data-line="11">  <span style="color: #e0e2ea;">Usage</span><span style="color: #e0e2ea;">:</span>     <span style="color: #e0e2ea;">57.9</span><span style="color: #e0e2ea;">%</span>
</div></code></pre>
<h3><a href="#shellawk-parsing-df-output" aria-hidden="true" class="anchor" id="shellawk-parsing-df-output"></a>Shell/awk: Parsing <code>df</code> output</h3>
<pre class="athl" style="color: #e0e2ea; background-color: #14161b;"><code class="language-bash" translate="no" tabindex="0"><div class="line" data-line="1"><span style="color: #e0e2ea; font-weight: bold;">#!/bin/bash</span>
</div><div class="line" data-line="2">
</div><div class="line" data-line="3"><span style="color: #8cf8f7;">df</span> <span style="color: #e0e2ea;">-k</span> <span style="color: #e0e2ea;">/</span> <span style="color: #e0e2ea;">/System/Volumes/Data</span> <span style="color: #e0e2ea;">|</span> <span style="color: #8cf8f7;">awk</span> <span style="color: #b3f6c0;">&#39;</span>
</div><div class="line" data-line="4"><span style="color: #b3f6c0;">NR &gt; 1 &lbrace;</span>
</div><div class="line" data-line="5"><span style="color: #b3f6c0;">    total = $2 / (1024 * 1024)</span>
</div><div class="line" data-line="6"><span style="color: #b3f6c0;">    used = $3 / (1024 * 1024)</span>
</div><div class="line" data-line="7"><span style="color: #b3f6c0;">    avail = $4 / (1024 * 1024)</span>
</div><div class="line" data-line="8"><span style="color: #b3f6c0;">    percent = (used / total) * 100</span>
</div><div class="line" data-line="9"><span style="color: #b3f6c0;"></span>
</div><div class="line" data-line="10"><span style="color: #b3f6c0;">    printf &quot;%s:\n&quot;, $9</span>
</div><div class="line" data-line="11"><span style="color: #b3f6c0;">    printf &quot;  Total:     %.2f GiB\n&quot;, total</span>
</div><div class="line" data-line="12"><span style="color: #b3f6c0;">    printf &quot;  Used:      %.2f GiB\n&quot;, used</span>
</div><div class="line" data-line="13"><span style="color: #b3f6c0;">    printf &quot;  Available: %.2f GiB\n&quot;, avail</span>
</div><div class="line" data-line="14"><span style="color: #b3f6c0;">    printf &quot;  Usage:     %.1f%%\n\n&quot;, percent</span>
</div><div class="line" data-line="15"><span style="color: #b3f6c0;">&rbrace;&#39;</span>
</div></code></pre>
<p>Output:</p>
<pre class="athl" style="color: #e0e2ea; background-color: #14161b;"><code class="language-bash" translate="no" tabindex="0"><div class="line" data-line="1"><span style="color: #8cf8f7;">/:</span>
</div><div class="line" data-line="2">  <span style="color: #8cf8f7;">Total:</span>     <span style="color: #e0e2ea;">460.43</span> <span style="color: #e0e2ea;">GiB</span>
</div><div class="line" data-line="3">  <span style="color: #8cf8f7;">Used:</span>      <span style="color: #e0e2ea;">13.98</span> <span style="color: #e0e2ea;">GiB</span>
</div><div class="line" data-line="4">  <span style="color: #8cf8f7;">Available:</span> <span style="color: #e0e2ea;">194.07</span> <span style="color: #e0e2ea;">GiB</span>
</div><div class="line" data-line="5">  <span style="color: #8cf8f7;">Usage:</span>     <span style="color: #e0e2ea;">3.0%</span>
</div><div class="line" data-line="6">
</div><div class="line" data-line="7"><span style="color: #8cf8f7;">/System/Volumes/Data:</span>
</div><div class="line" data-line="8">  <span style="color: #8cf8f7;">Total:</span>     <span style="color: #e0e2ea;">460.43</span> <span style="color: #e0e2ea;">GiB</span>
</div><div class="line" data-line="9">  <span style="color: #8cf8f7;">Used:</span>      <span style="color: #e0e2ea;">236.35</span> <span style="color: #e0e2ea;">GiB</span>
</div><div class="line" data-line="10">  <span style="color: #8cf8f7;">Available:</span> <span style="color: #e0e2ea;">194.07</span> <span style="color: #e0e2ea;">GiB</span>
</div><div class="line" data-line="11">  <span style="color: #8cf8f7;">Usage:</span>     <span style="color: #e0e2ea;">51.3%</span>
</div></code></pre>
<p>Note: The shell/awk script shows different "Used" values for each volume because it's querying each volume individually, while the Python script shows the total used space across the volume group.</p>
<h2><a href="#gb-vs-gib" aria-hidden="true" class="anchor" id="gb-vs-gib"></a>GB vs GiB</h2>
<p>Why does <code>df</code> show 460Gi while Finder shows 494GB?</p>
<p>Manufacturers use decimal (base-10):</p>
<ul>
<li>1 GB = 1,000,000,000 bytes</li>
</ul>
<p>Operating systems use binary (base-2):</p>
<ul>
<li>1 GiB = 1,073,741,824 bytes (2^30)</li>
</ul>
<pre class="athl" style="color: #e0e2ea; background-color: #14161b;"><code class="language-bash" translate="no" tabindex="0"><div class="line" data-line="1"><span style="color: #e0e2ea;">494</span> <span style="color: #e0e2ea;">GB</span> <span style="color: #e0e2ea;">=</span> <span style="color: #e0e2ea;">494</span> <span style="color: #e0e2ea;">×</span> <span style="color: #e0e2ea;">1,000,000,000</span> <span style="color: #e0e2ea;">=</span> <span style="color: #e0e2ea;">494,000,000,000</span> <span style="color: #e0e2ea;">bytes</span>
</div><div class="line" data-line="2"><span style="color: #8cf8f7;">494,000,000,000</span> <span style="color: #e0e2ea;">bytes</span> <span style="color: #e0e2ea;">÷</span> <span style="color: #e0e2ea;">1,073,741,824</span> <span style="color: #e0e2ea;">=</span> <span style="color: #e0e2ea;">460.1</span> <span style="color: #e0e2ea;">GiB</span>
</div></code></pre>
<p>They're measuring the same space with different units. Finder uses GB (decimal), <code>df</code> uses GiB (binary).</p>
<h2><a href="#but-wait-where-did-my-6-gb-go" aria-hidden="true" class="anchor" id="but-wait-where-did-my-6-gb-go"></a>But Wait, Where Did My 6 GB Go?</h2>
<p>Finder shows 494.38 GB total capacity. But I bought a 500GB drive. Where did the other ~6 GB go?</p>
<p>Turns out Apple officially documents this in <a href="https://support.apple.com/en-us/102119">support article 102119</a>: system partitions and formatting overhead eat up space that Finder doesn't show.</p>
<p>Here's where it went:</p>
<pre class="athl" style="color: #e0e2ea; background-color: #14161b;"><code class="language-bash" translate="no" tabindex="0"><div class="line" data-line="1"><span style="color: #8cf8f7;">$</span> <span style="color: #e0e2ea;">diskutil</span> <span style="color: #e0e2ea;">list</span>
</div><div class="line" data-line="2"><span style="color: #8cf8f7;">/dev/disk0</span> <span style="color: #e0e2ea;">(</span><span style="color: #8cf8f7;">internal,</span> <span style="color: #e0e2ea;">physical</span><span style="color: #e0e2ea;">)</span>:
</div><div class="line" data-line="3">   #:                       <span style="color: #8cf8f7;">TYPE</span> <span style="color: #e0e2ea;">NAME</span>                    <span style="color: #e0e2ea;">SIZE</span>       <span style="color: #e0e2ea;">IDENTIFIER</span>
</div><div class="line" data-line="4">   <span style="color: #8cf8f7;">0:</span>      <span style="color: #e0e2ea;">GUID_partition_scheme</span>                        <span style="color: #e0e2ea;">*500.3</span> <span style="color: #e0e2ea;">GB</span>   <span style="color: #e0e2ea;">disk0</span>
</div><div class="line" data-line="5">   <span style="color: #8cf8f7;">1:</span>             <span style="color: #e0e2ea;">Apple_APFS_ISC</span> <span style="color: #e0e2ea;">Container</span> <span style="color: #e0e2ea;">disk1</span>         <span style="color: #e0e2ea;">524.3</span> <span style="color: #e0e2ea;">MB</span>   <span style="color: #e0e2ea;">disk0s1</span>
</div><div class="line" data-line="6">   <span style="color: #8cf8f7;">2:</span>                 <span style="color: #e0e2ea;">Apple_APFS</span> <span style="color: #e0e2ea;">Container</span> <span style="color: #e0e2ea;">disk3</span>         <span style="color: #e0e2ea;">494.4</span> <span style="color: #e0e2ea;">GB</span>   <span style="color: #e0e2ea;">disk0s2</span>
</div><div class="line" data-line="7">   <span style="color: #8cf8f7;">3:</span>        <span style="color: #e0e2ea;">Apple_APFS_Recovery</span> <span style="color: #e0e2ea;">Container</span> <span style="color: #e0e2ea;">disk2</span>         <span style="color: #e0e2ea;">5.4</span> <span style="color: #e0e2ea;">GB</span>     <span style="color: #e0e2ea;">disk0s3</span>
</div></code></pre>
<p>Breaking down my 500GB drive:</p>
<pre class="athl" style="color: #e0e2ea; background-color: #14161b;"><code class="language-bash" translate="no" tabindex="0"><div class="line" data-line="1"><span style="color: #8cf8f7;">Raw</span> <span style="color: #e0e2ea;">disk</span> <span style="color: #e0e2ea;">capacity:</span>                   <span style="color: #e0e2ea;">500.3</span> <span style="color: #e0e2ea;">GB</span><span style="color: #e0e2ea;"></span> <span style="color: #e0e2ea;">(</span><span style="color: #8cf8f7;">from</span> <span style="color: #e0e2ea;">diskutil</span><span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="2"><span style="color: #8cf8f7;">Apple_APFS_Recovery</span> <span style="color: #e0e2ea;">partition:</span>        <span style="color: #e0e2ea;">-5.4</span> <span style="color: #e0e2ea;">GB</span>
</div><div class="line" data-line="3"><span style="color: #8cf8f7;">Apple_APFS_ISC</span> <span style="color: #e0e2ea;">partition:</span>             <span style="color: #e0e2ea;">-0.5</span> <span style="color: #e0e2ea;">GB</span><span style="color: #e0e2ea;"></span> <span style="color: #e0e2ea;">(</span><span style="color: #8cf8f7;">524.3</span> <span style="color: #e0e2ea;">MB</span><span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="4"><span style="color: #8cf8f7;">------------------------------------------------</span>
</div><div class="line" data-line="5"><span style="color: #8cf8f7;">Available</span> <span style="color: #e0e2ea;">to</span> <span style="color: #e0e2ea;">APFS</span> <span style="color: #e0e2ea;">container:</span>         <span style="color: #e0e2ea;">494.4</span> <span style="color: #e0e2ea;">GB</span>
</div><div class="line" data-line="6"><span style="color: #8cf8f7;">Finder</span> <span style="color: #e0e2ea;">shows:</span>                        <span style="color: #e0e2ea;">494.38</span> <span style="color: #e0e2ea;">GB</span> <span style="color: #e0e2ea;">✓</span>
</div></code></pre>
<p>The "missing" ~6 GB is sitting in hidden system partitions:</p>
<ul>
<li><strong>Apple_APFS_Recovery</strong> (5.4 GB): Recovery OS for reinstalling macOS</li>
<li><strong>Apple_APFS_ISC</strong> (524 MB): Secure boot and firmware updates</li>
</ul>
<p>These partitions are real, necessary, and invisible to Finder. You didn't lose the storage; it's just working behind the scenes.</p>
<h2><a href="#summary" aria-hidden="true" class="anchor" id="summary"></a>Summary</h2>
<ul>
<li><strong>Finder</strong>: Uses <code>statfs()</code>/<code>getattrlist()</code> APIs for a user-friendly view, but can show misleading figures with APFS snapshots and purgeable space.</li>
<li><strong><code>du</code></strong>: Walks the directory tree. Doesn't see snapshots or understand APFS copy-on-write behavior.</li>
<li><strong><code>df</code></strong>: Queries filesystem metadata. Check both <code>/</code> (system volume) and <code>/System/Volumes/Data</code> (your files).</li>
<li><strong>Programmatic</strong>: Use <code>os.statvfs()</code> (Python) or parse <code>df</code> output (shell/awk).</li>
<li><strong>For precision</strong>: Use <code>diskutil apfs list</code>, <code>tmutil listlocalsnapshots</code>, and understand that APFS block sharing complicates space accounting.</li>
</ul>
<p>Every tool is telling the truth. They're just answering different questions.</p> ]]></description>
    </item>
    <item>
       <title>How banks create money (with your money)</title>
       <link>https://www.mikenotthepope.com/how-banks-create-money-with-your-money/</link>
       <pubDate>Wed, 15 Oct 2025 00:00:00 UTC</pubDate>
       <guid>https://www.mikenotthepope.com/how-banks-create-money-with-your-money/</guid>
       <description><![CDATA[ <p>Let's say NewBank just opened its first branch. They start off with $0 in customer deposits. You walk into the bank with a big sack of cash with $100K in it and the bank manager says, "Hey, it looks like we have our first customer!"</p>
<p>You open an account and now the bank has $100K. The bank manager wants to make money, and for some reason you decide you wanna borrow from the bank and stick the money in your account. You ask the bank manager "how much can I borrow?" to which the manager replies, "you can borrow up to 90K".</p>
<p>There's now 190K in your account. Wait a minute, the bank has only accepted 100K in deposits, and yet your bank account now has 190K. WTF? Where did the 90K come from? Simple, the bank created it!</p>
<p>Here's what happened:</p>
<ol>
<li>You lent the bank 100K.</li>
<li>The bank lent you 90K.</li>
<li>You and the bank have borrowed from each other!</li>
</ol>
<p>OK, so now you want to take your money out:</p>
<ul>
<li>You: "How much can I withdraw?"</li>
<li>Manager: "100K"</li>
<li>You: "OK, give me 100K"</li>
<li>Manager: "Sure, but you still owe us 90K"</li>
<li>You: "OK, fine, here's 90K"</li>
<li>Manager: "Thank you sir. Would you like to withdraw your last 90K?"</li>
<li>You: "Yes please"</li>
</ul>
<p>Money creation is the bank lending out your money:</p>
<ol>
<li>You have 100K, the bank has $0.</li>
<li>You make a deposit, now you have $0 and the bank has 100K.</li>
<li>The bank lends you 90K, now your account shows 190K.</li>
<li>The bank only has 10K in reserves but owes you 190K.</li>
<li>The money supply just went from 100K to 190K.</li>
<li>That extra 90K? The bank created it out of thin air.</li>
<li>When you repay the 90K loan, that money disappears. It wasn't real money, it was debt.</li>
</ol>
<p>In summary, if you wanna go further down the rabbit hole, here's <a href="https://youtu.be/cDNSNX48Kmo?si=1ofNaXHNdAFXw1wG">a video by Money & Macro</a> that explains it in more detail.</p> ]]></description>
    </item>
    <item>
       <title>Primary keys using UUID v7 are (potentially) an HR violation</title>
       <link>https://www.mikenotthepope.com/primary-keys-using-uuid-v7-are-potentially-an-hr-violation/</link>
       <pubDate>Mon, 06 Oct 2025 00:00:00 UTC</pubDate>
       <guid>https://www.mikenotthepope.com/primary-keys-using-uuid-v7-are-potentially-an-hr-violation/</guid>
       <description><![CDATA[ <p>I'm building an applicant tracking system where I use <a href="https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_7_(timestamp_and_random)">UUID v7</a> for primary keys on the users table. Then I discovered an unintended consequence: UUID v7 embeds a timestamp, which creates a privacy leak. If a user's account was created in 2025 with an ID of <code>0199b901-fb2b-7745-9e62-019fe1c0ddca</code>, anyone can extract that timestamp and infer a minimum age for the user. The older the account, the older we know the applicant must be, enabling unintentional age discrimination through what should be an opaque identifier.</p>
<h2><a href="#the-problem" aria-hidden="true" class="anchor" id="the-problem"></a>The Problem</h2>
<p>UUID v7 embeds a timestamp in its first 48 bits. Anyone can extract it:</p>
<pre class="athl" style="color: #e0e2ea; background-color: #14161b;"><code class="language-elixir" translate="no" tabindex="0"><div class="line" data-line="1"><span style="color: #e0e2ea; font-weight: bold;">defmodule</span> <span style="color: #e0e2ea;">DinosaurDetector</span> <span style="color: #e0e2ea; font-weight: bold;">do</span>
</div><div class="line" data-line="2">  <span style="color: #9b9ea4;"># Assume youngest applicant is 14.</span>
</div><div class="line" data-line="3">  <span style="color: #9b9ea4;"># If application &gt; 16 years old,</span>
</div><div class="line" data-line="4">  <span style="color: #9b9ea4;"># they must be 30+ now (14 + 16).</span>
</div><div class="line" data-line="5">  <span style="color: #e0e2ea; font-weight: bold;">def</span> <span style="color: #8cf8f7;">old?</span><span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">datetime</span><span style="color: #e0e2ea;">)</span> <span style="color: #e0e2ea; font-weight: bold;">do</span>
</div><div class="line" data-line="6">    <span style="color: #e0e2ea;">years</span> <span style="color: #e0e2ea;">=</span> <span style="color: #e0e2ea;">DateTime</span><span style="color: #e0e2ea;">.</span><span style="color: #8cf8f7;">diff</span><span style="color: #e0e2ea;">(</span>
</div><div class="line" data-line="7">      <span style="color: #e0e2ea;">DateTime</span><span style="color: #e0e2ea;">.</span><span style="color: #8cf8f7;">utc_now</span><span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">)</span><span style="color: #e0e2ea;">,</span>
</div><div class="line" data-line="8">      <span style="color: #e0e2ea;">datetime</span><span style="color: #e0e2ea;">,</span>
</div><div class="line" data-line="9">      <span style="color: #8cf8f7;">:year</span>
</div><div class="line" data-line="10">    <span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="11">    <span style="color: #e0e2ea;">years</span> <span style="color: #e0e2ea;">&gt;</span> <span style="color: #e0e2ea;">16</span>
</div><div class="line" data-line="12">  <span style="color: #e0e2ea; font-weight: bold;">end</span>
</div><div class="line" data-line="13"><span style="color: #e0e2ea; font-weight: bold;">end</span>
</div><div class="line" data-line="14">
</div><div class="line" data-line="15"><span style="color: #9b9ea4;"># ID created in 2025</span>
</div><div class="line" data-line="16"><span style="color: #e0e2ea;">iex</span><span style="color: #e0e2ea;">&gt;</span> <span style="color: #e0e2ea;">id</span> <span style="color: #e0e2ea;">=</span> <span style="color: #e0e2ea;">UUID</span><span style="color: #e0e2ea;">.</span><span style="color: #8cf8f7;">uuid7</span><span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="17"><span style="color: #b3f6c0;">&quot;0199b901-fb2b-7745-9e62-019fe1c0ddca&quot;</span>
</div><div class="line" data-line="18">
</div><div class="line" data-line="19"><span style="color: #9b9ea4;"># Extract embedded timestamp</span>
</div><div class="line" data-line="20"><span style="color: #e0e2ea;">iex</span><span style="color: #e0e2ea;">&gt;</span> <span style="color: #e0e2ea;">&lt;&lt;</span><span style="color: #e0e2ea;">ts</span><span style="color: #e0e2ea;">::</span><span style="color: #e0e2ea;">48</span><span style="color: #e0e2ea;">,</span> <span style="color: #9b9ea4;">_</span><span style="color: #e0e2ea;">::</span><span style="color: #e0e2ea;">80</span><span style="color: #e0e2ea;">&gt;&gt;</span> <span style="color: #e0e2ea;">=</span>
</div><div class="line" data-line="21">  <span style="color: #e0e2ea;">UUID</span><span style="color: #e0e2ea;">.</span><span style="color: #8cf8f7;">string_to_binary!</span><span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">id</span><span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="22"><span style="color: #e0e2ea;">iex</span><span style="color: #e0e2ea;">&gt;</span> <span style="color: #e0e2ea;">applied</span> <span style="color: #e0e2ea;">=</span>
</div><div class="line" data-line="23">  <span style="color: #e0e2ea;">DateTime</span><span style="color: #e0e2ea;">.</span><span style="color: #8cf8f7;">from_unix!</span><span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">ts</span><span style="color: #e0e2ea;">,</span> <span style="color: #8cf8f7;">:millisecond</span><span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="24"><span style="color: #8cf8f7;">~</span>U<span style="color: #e0e2ea;">[</span>2025-10-06 10:12:18.859Z<span style="color: #e0e2ea;">]</span>
</div><div class="line" data-line="25">
</div><div class="line" data-line="26"><span style="color: #9b9ea4;"># In 2041, this is 16 years old</span>
</div><div class="line" data-line="27"><span style="color: #e0e2ea;">iex</span><span style="color: #e0e2ea;">&gt;</span> <span style="color: #e0e2ea;">DinosaurDetector</span><span style="color: #e0e2ea;">.</span><span style="color: #8cf8f7;">old?</span><span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">applied</span><span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="28"><span style="color: #e0e2ea;">true</span>
</div></code></pre>
<p>In an ATS, that timestamp reveals when someone applied. Since I'm building the best ATS in the world, I should assume my users will be around long enough to be discriminated against. Someone who applied in 2025 shouldn't be discriminated against in 2041 just because their ID is older than ChatGPT. But if their ID says they applied in 2025, and it's now 2041, that UUID becomes a timestamp that says "this person is old."</p>
<h2><a href="#its-not-just-uuid-v7" aria-hidden="true" class="anchor" id="its-not-just-uuid-v7"></a>It's Not Just UUID v7</h2>
<p>Any time-sortable ID has this problem:</p>
<p><strong><a href="https://en.wikipedia.org/wiki/Snowflake_ID">Snowflake IDs</a>:</strong></p>
<pre class="athl" style="color: #e0e2ea; background-color: #14161b;"><code class="language-elixir" translate="no" tabindex="0"><div class="line" data-line="1"><span style="color: #e0e2ea;">iex</span><span style="color: #e0e2ea;">&gt;</span> <span style="color: #e0e2ea;">id</span> <span style="color: #e0e2ea;">=</span> <span style="color: #e0e2ea;">495890505138176</span>
</div><div class="line" data-line="2"><span style="color: #e0e2ea;">iex</span><span style="color: #e0e2ea;">&gt;</span> <span style="color: #e0e2ea;">ts</span> <span style="color: #e0e2ea;">=</span> <span style="color: #e0e2ea;">Bitwise</span><span style="color: #e0e2ea;">.</span><span style="color: #8cf8f7;">bsr</span><span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">id</span><span style="color: #e0e2ea;">,</span> <span style="color: #e0e2ea;">22</span><span style="color: #e0e2ea;">)</span> <span style="color: #e0e2ea;">+</span>
</div><div class="line" data-line="3">  <span style="color: #e0e2ea;">1_288_834_974_657</span>
</div><div class="line" data-line="4"><span style="color: #e0e2ea;">iex</span><span style="color: #e0e2ea;">&gt;</span> <span style="color: #e0e2ea;">applied</span> <span style="color: #e0e2ea;">=</span>
</div><div class="line" data-line="5">  <span style="color: #e0e2ea;">DateTime</span><span style="color: #e0e2ea;">.</span><span style="color: #8cf8f7;">from_unix!</span><span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">ts</span><span style="color: #e0e2ea;">,</span> <span style="color: #8cf8f7;">:millisecond</span><span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="6"><span style="color: #8cf8f7;">~</span>U<span style="color: #e0e2ea;">[</span>2025-10-06 10:12:18.859Z<span style="color: #e0e2ea;">]</span>
</div><div class="line" data-line="7">
</div><div class="line" data-line="8"><span style="color: #e0e2ea;">iex</span><span style="color: #e0e2ea;">&gt;</span> <span style="color: #e0e2ea;">DinosaurDetector</span><span style="color: #e0e2ea;">.</span><span style="color: #8cf8f7;">old?</span><span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">applied</span><span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="9"><span style="color: #e0e2ea;">true</span>
</div></code></pre>
<p><strong><a href="https://en.wikipedia.org/wiki/MongoDB#Object_ID">MongoDB ObjectIDs</a>:</strong></p>
<pre class="athl" style="color: #e0e2ea; background-color: #14161b;"><code class="language-elixir" translate="no" tabindex="0"><div class="line" data-line="1"><span style="color: #e0e2ea;">iex</span><span style="color: #e0e2ea;">&gt;</span> <span style="color: #e0e2ea;">id</span> <span style="color: #e0e2ea;">=</span> <span style="color: #b3f6c0;">&quot;6702538a0000000000000000&quot;</span>
</div><div class="line" data-line="2"><span style="color: #e0e2ea;">iex</span><span style="color: #e0e2ea;">&gt;</span> <span style="color: #e0e2ea;">&lt;&lt;</span><span style="color: #e0e2ea;">ts</span><span style="color: #e0e2ea;">::</span><span style="color: #e0e2ea;">32</span><span style="color: #e0e2ea;">,</span> <span style="color: #9b9ea4;">_</span><span style="color: #e0e2ea;">::</span><span style="color: #e0e2ea;">96</span><span style="color: #e0e2ea;">&gt;&gt;</span> <span style="color: #e0e2ea;">=</span>
</div><div class="line" data-line="3">  <span style="color: #e0e2ea;">Base</span><span style="color: #e0e2ea;">.</span><span style="color: #8cf8f7;">decode16!</span><span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">id</span><span style="color: #e0e2ea;">,</span> <span style="color: #8cf8f7;">case: </span><span style="color: #8cf8f7;">:lower</span><span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="4"><span style="color: #e0e2ea;">iex</span><span style="color: #e0e2ea;">&gt;</span> <span style="color: #e0e2ea;">applied</span> <span style="color: #e0e2ea;">=</span>
</div><div class="line" data-line="5">  <span style="color: #e0e2ea;">DateTime</span><span style="color: #e0e2ea;">.</span><span style="color: #8cf8f7;">from_unix!</span><span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">ts</span><span style="color: #e0e2ea;">,</span> <span style="color: #8cf8f7;">:second</span><span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="6"><span style="color: #8cf8f7;">~</span>U<span style="color: #e0e2ea;">[</span>2025-10-06 10:12:18Z<span style="color: #e0e2ea;">]</span>
</div><div class="line" data-line="7">
</div><div class="line" data-line="8"><span style="color: #e0e2ea;">iex</span><span style="color: #e0e2ea;">&gt;</span> <span style="color: #e0e2ea;">DinosaurDetector</span><span style="color: #e0e2ea;">.</span><span style="color: #8cf8f7;">old?</span><span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">applied</span><span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="9"><span style="color: #e0e2ea;">true</span>
</div></code></pre>
<p><a href="https://en.wikipedia.org/wiki/Universally_unique_identifier#ULID">ULID</a>, KSUID, and Instagram IDs all encode timestamps for database performance too. They work well for indexes but violate <a href="https://en.wikipedia.org/wiki/Age_Discrimination_in_Employment_Act_of_1967">age discrimination laws</a>.</p>
<h2><a href="#the-fix" aria-hidden="true" class="anchor" id="the-fix"></a>The Fix</h2>
<p><strong>Use UUID v4 for applicant records:</strong></p>
<pre class="athl" style="color: #e0e2ea; background-color: #14161b;"><code class="language-elixir" translate="no" tabindex="0"><div class="line" data-line="1"><span style="color: #e0e2ea;">iex</span><span style="color: #e0e2ea;">&gt;</span> <span style="color: #e0e2ea;">id</span> <span style="color: #e0e2ea;">=</span> <span style="color: #e0e2ea;">UUID</span><span style="color: #e0e2ea;">.</span><span style="color: #8cf8f7;">uuid4</span><span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="2"><span style="color: #b3f6c0;">&quot;f81d4fae-7dec-11d0-a765-00a0c91e6bf6&quot;</span>
</div><div class="line" data-line="3">
</div><div class="line" data-line="4"><span style="color: #9b9ea4;"># Try to extract timestamp</span>
</div><div class="line" data-line="5"><span style="color: #9b9ea4;"># (meaningless from random data)</span>
</div><div class="line" data-line="6"><span style="color: #e0e2ea;">iex</span><span style="color: #e0e2ea;">&gt;</span> <span style="color: #e0e2ea;">&lt;&lt;</span><span style="color: #e0e2ea;">ts</span><span style="color: #e0e2ea;">::</span><span style="color: #e0e2ea;">48</span><span style="color: #e0e2ea;">,</span> <span style="color: #9b9ea4;">_</span><span style="color: #e0e2ea;">::</span><span style="color: #e0e2ea;">80</span><span style="color: #e0e2ea;">&gt;&gt;</span> <span style="color: #e0e2ea;">=</span>
</div><div class="line" data-line="7">  <span style="color: #e0e2ea;">UUID</span><span style="color: #e0e2ea;">.</span><span style="color: #8cf8f7;">string_to_binary!</span><span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">id</span><span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="8"><span style="color: #e0e2ea;">iex</span><span style="color: #e0e2ea;">&gt;</span> <span style="color: #e0e2ea;">applied</span> <span style="color: #e0e2ea;">=</span>
</div><div class="line" data-line="9">  <span style="color: #e0e2ea;">DateTime</span><span style="color: #e0e2ea;">.</span><span style="color: #8cf8f7;">from_unix!</span><span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">ts</span><span style="color: #e0e2ea;">,</span> <span style="color: #8cf8f7;">:millisecond</span><span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="10"><span style="color: #8cf8f7;">~</span>U<span style="color: #e0e2ea;">[</span>5623-10-21 00:54:32.000Z<span style="color: #e0e2ea;">]</span>
</div><div class="line" data-line="11">
</div><div class="line" data-line="12"><span style="color: #e0e2ea;">iex</span><span style="color: #e0e2ea;">&gt;</span> <span style="color: #e0e2ea;">DinosaurDetector</span><span style="color: #e0e2ea;">.</span><span style="color: #8cf8f7;">old?</span><span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">applied</span><span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="13"><span style="color: #e0e2ea;">false</span>  <span style="color: #9b9ea4;"># Immune!</span>
</div></code></pre>
<p><strong>Use UUID v7 for non-sensitive data:</strong></p>
<pre class="athl" style="color: #e0e2ea; background-color: #14161b;"><code class="language-elixir" translate="no" tabindex="0"><div class="line" data-line="1"><span style="color: #e0e2ea;">job_posting_id</span> <span style="color: #e0e2ea;">=</span> <span style="color: #e0e2ea;">UUID</span><span style="color: #e0e2ea;">.</span><span style="color: #8cf8f7;">uuid7</span><span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="2"><span style="color: #e0e2ea;">interview_id</span> <span style="color: #e0e2ea;">=</span> <span style="color: #e0e2ea;">UUID</span><span style="color: #e0e2ea;">.</span><span style="color: #8cf8f7;">uuid7</span><span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">)</span>
</div></code></pre>
<p><strong>Or encrypt time-sortable IDs before exposing them:</strong></p>
<pre class="athl" style="color: #e0e2ea; background-color: #14161b;"><code class="language-elixir" translate="no" tabindex="0"><div class="line" data-line="1"><span style="color: #e0e2ea; font-weight: bold;">defmodule</span> <span style="color: #e0e2ea;">SecureID</span> <span style="color: #e0e2ea; font-weight: bold;">do</span>
</div><div class="line" data-line="2">  <span style="color: #9b9ea4;"># Simplified - needs proper key</span>
</div><div class="line" data-line="3">  <span style="color: #9b9ea4;"># management in production</span>
</div><div class="line" data-line="4">  <span style="color: #e0e2ea; font-weight: bold;">def</span> <span style="color: #8cf8f7;">encrypt</span><span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">uuid</span><span style="color: #e0e2ea;">)</span> <span style="color: #e0e2ea; font-weight: bold;">do</span>
</div><div class="line" data-line="5">    <span style="color: #e0e2ea;">key</span> <span style="color: #e0e2ea;">=</span> <span style="color: #e0e2ea;">:crypto</span><span style="color: #e0e2ea;">.</span><span style="color: #8cf8f7;">strong_rand_bytes</span><span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">32</span><span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="6">
</div><div class="line" data-line="7">    <span style="color: #e0e2ea;">:crypto</span><span style="color: #e0e2ea;">.</span><span style="color: #8cf8f7;">crypto_one_time</span><span style="color: #e0e2ea;">(</span>
</div><div class="line" data-line="8">      <span style="color: #8cf8f7;">:aes_256_cbc</span><span style="color: #e0e2ea;">,</span>
</div><div class="line" data-line="9">      <span style="color: #e0e2ea;">key</span><span style="color: #e0e2ea;">,</span>
</div><div class="line" data-line="10">      <span style="color: #e0e2ea;">uuid</span><span style="color: #e0e2ea;">,</span>
</div><div class="line" data-line="11">      <span style="color: #e0e2ea;">true</span>
</div><div class="line" data-line="12">    <span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="13">    <span style="color: #e0e2ea;">|&gt;</span> <span style="color: #e0e2ea;">Base</span><span style="color: #e0e2ea;">.</span><span style="color: #8cf8f7;">encode64</span><span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="14">  <span style="color: #e0e2ea; font-weight: bold;">end</span>
</div><div class="line" data-line="15"><span style="color: #e0e2ea; font-weight: bold;">end</span>
</div><div class="line" data-line="16">
</div><div class="line" data-line="17"><span style="color: #e0e2ea;">internal</span> <span style="color: #e0e2ea;">=</span> <span style="color: #e0e2ea;">UUID</span><span style="color: #e0e2ea;">.</span><span style="color: #8cf8f7;">uuid7</span><span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">)</span>
</div><div class="line" data-line="18"><span style="color: #e0e2ea;">public</span> <span style="color: #e0e2ea;">=</span> <span style="color: #e0e2ea;">SecureID</span><span style="color: #e0e2ea;">.</span><span style="color: #8cf8f7;">encrypt</span><span style="color: #e0e2ea;">(</span><span style="color: #e0e2ea;">internal</span><span style="color: #e0e2ea;">)</span>
</div></code></pre>
<h2><a href="#the-lesson" aria-hidden="true" class="anchor" id="the-lesson"></a>The Lesson</h2>
<p>By optimizing for database performance, I would be creating a compliance risk. UUID v7 identifiers leak everywhere: URLs, logs, API responses, analytics. Unlike <code>created_at</code> fields, they're treated as opaque when they actually contain information.</p>
<p>The ATS should not encode protected characteristics in its identifiers.</p>
<p>I'll use UUID v4 for applicant IDs. The database team will grumble about performance. I'll sleep well. That's the tradeoff.</p> ]]></description>
    </item>
  </channel>
</rss>
