<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Fred Santana</title>
    <description>The latest articles on DEV Community by Fred Santana (@fredsanntana).</description>
    <link>https://dev.to/fredsanntana</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3806652%2Faad2d28d-415b-4e27-ac6c-a535837ab191.png</url>
      <title>DEV Community: Fred Santana</title>
      <link>https://dev.to/fredsanntana</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fredsanntana"/>
    <language>en</language>
    <item>
      <title>Surviving Midnight SDK: a 700-line cure for the silent failure problem</title>
      <dc:creator>Fred Santana</dc:creator>
      <pubDate>Mon, 27 Apr 2026 23:09:46 +0000</pubDate>
      <link>https://dev.to/midnight-aliit/surviving-midnight-sdk-a-700-line-cure-for-the-silent-failure-problem-57p</link>
      <guid>https://dev.to/midnight-aliit/surviving-midnight-sdk-a-700-line-cure-for-the-silent-failure-problem-57p</guid>
      <description>&lt;p&gt;Why I built midnight-doctor — a pre-flight check that catches the 16 hours of debugging you don't know you're about to spend&lt;/p&gt;

&lt;p&gt;"The information you need to align your stack already exists. It's just not executable."*&lt;/p&gt;

&lt;p&gt;After five months building four applications on &lt;a href="https://midnight.network" rel="noopener noreferrer"&gt;Midnight Network&lt;/a&gt;, I've concluded the protocol isn't the problem. The protocol is genuinely good — Compact compiles ZK circuits in three seconds without a trusted setup ceremony, and the shielded/unshielded/dust split is the cleanest answer to compliance-grade privacy I've seen.&lt;/p&gt;

&lt;p&gt;The problem is &lt;strong&gt;everything that surrounds it&lt;/strong&gt;. Specifically, the unforgiving math of: a fast-moving SDK + multiple environment tracks + a single npm namespace + zero error messages when versions misalign.&lt;/p&gt;

&lt;p&gt;This post is the story of one specific 6-hour debugging session, the pattern I extracted from it, and the &lt;a href="https://github.com/fredericosanntana/midnight-doctor" rel="noopener noreferrer"&gt;~700-line tool I built&lt;/a&gt; so the next person doesn't pay the same tax.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 6-hour silent failure
&lt;/h2&gt;

&lt;p&gt;DPO2U Wallet, March 2026. I bumped &lt;code&gt;@midnight-ntwrk/wallet-sdk-facade&lt;/code&gt; to &lt;code&gt;2.0.0&lt;/code&gt; because npm marked it as &lt;code&gt;latest&lt;/code&gt;. My local stack ran &lt;code&gt;midnightntwrk/midnight-node:0.21.0&lt;/code&gt; (preprod-targeted). I wrote ~50 lines of wallet bootstrap, started the dev loop, and watched:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wallet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;WalletFacade&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;shielded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;unshielded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;dust&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForSyncedState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;synced!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;synced!&lt;/code&gt; line never printed.&lt;/p&gt;

&lt;p&gt;No error. No timeout. No log. The wallet just sat in &lt;code&gt;syncing&lt;/code&gt; state forever. I added more logging — got more "syncing" lines. Restarted Docker. Wiped state. Tried a fresh seed. Re-cloned the repo. Asked Discord. Read the SDK changelog (which didn't exist for that version). Stared at the indexer logs for 40 minutes.&lt;/p&gt;

&lt;p&gt;Six hours in, almost by accident, I noticed the symptom: &lt;code&gt;subscribeRuntimeVersion&lt;/code&gt; was firing once, returning, and never firing again. The standalone node closes that subscription early. The &lt;code&gt;WalletFacade.init({...})&lt;/code&gt; constructor in 2.x wires sync to that subscription. &lt;strong&gt;Result: a single missed event silently kills the entire sync loop, with zero surface.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The fix was to downgrade to facade 1.0.0 (preprod track). Forty seconds of work, after six hours of debugging. The bug was real but the cost was &lt;em&gt;information asymmetry&lt;/em&gt;: nothing in the documentation, npm metadata, or runtime told me that "this SDK version is incompatible with this node version."&lt;/p&gt;

&lt;p&gt;That asymmetry is the real bug. Not the runtime subscription closing. Not the constructor wiring. The fact that &lt;strong&gt;the system has the information needed to diagnose itself, but doesn't bother&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The pattern: silent failures from missing cross-references
&lt;/h2&gt;

&lt;p&gt;After that session, I started cataloguing other times I'd been bitten the same way. Within a week I had a list:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Root cause&lt;/th&gt;
&lt;th&gt;Time spent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;waitForSyncedState()&lt;/code&gt; hangs forever&lt;/td&gt;
&lt;td&gt;facade 2.x + node 0.21.0 mismatch&lt;/td&gt;
&lt;td&gt;~6h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;npm install&lt;/code&gt; returns ENOTFOUND&lt;/td&gt;
&lt;td&gt;community tutorial said &lt;code&gt;.npmrc&lt;/code&gt; should point at &lt;code&gt;npm.midnight.network&lt;/code&gt; (domain doesn't exist)&lt;/td&gt;
&lt;td&gt;~2h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transactions silently fail to submit&lt;/td&gt;
&lt;td&gt;Two versions of &lt;code&gt;@midnight-ntwrk/ledger-v7&lt;/code&gt; in &lt;code&gt;node_modules&lt;/code&gt; (transitive bump)&lt;/td&gt;
&lt;td&gt;~3h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Indexer crash-loops on startup&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;indexer-standalone:4.0.0-rc.4&lt;/code&gt; requires a &lt;code&gt;subscription:&lt;/code&gt; block in &lt;code&gt;indexer.yml&lt;/code&gt;, undocumented&lt;/td&gt;
&lt;td&gt;~1.5h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WalletFacade behaves differently on standalone vs preprod&lt;/td&gt;
&lt;td&gt;Same SDK, different node behavior, no warning&lt;/td&gt;
&lt;td&gt;~4h&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Across five incidents: &lt;strong&gt;~16 hours of debugging silent failures&lt;/strong&gt;. Each one had a root cause that was, with the right cross-reference, &lt;strong&gt;detectable in seconds&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;package.json&lt;/code&gt; says facade is 2.0&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker ps&lt;/code&gt; says node is 0.21.0&lt;/li&gt;
&lt;li&gt;A lookup table says those two are incompatible&lt;/li&gt;
&lt;li&gt;→ Print a clear error.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The system already has all three pieces of data. There's just no glue.&lt;/p&gt;




&lt;h2&gt;
  
  
  The tool: &lt;code&gt;midnight-doctor&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The fix isn't a new feature in the SDK. It isn't better docs (which decay). It's an &lt;strong&gt;executable cross-reference&lt;/strong&gt; — a script that reads your project, your Docker stack, and your config files; matches them against a curated compatibility table; and tells you what's wrong.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://github.com/fredericosanntana/midnight-doctor" rel="noopener noreferrer"&gt;&lt;code&gt;midnight-doctor&lt;/code&gt;&lt;/a&gt; over a weekend. ~700 lines of Node, zero runtime dependencies, single-binary install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx midnight-doctor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run against my own legacy &lt;code&gt;midnight-hello-world&lt;/code&gt; repo, it produces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;── midnight-doctor ──
project: /root/midnight-hello-world

⚠ SDK track: Preprod 3.x (legacy, OK for existing apps)
   Detected from wallet-sdk-facade@2.0.0.
⚠ Track is deprecated
   SDK 3.2 / facade 2.0 is 2 majors behind. Current is wallet-sdk@1.0.0 /
   facade@4.0.0 (released 2026-04-23). The WalletFacade.init({...}) constructor
   used in 2.0 has been removed; 4.0 reverted to `new WalletFacade(s, u, d) +
   .start()`. Plan a migration.
⚠ WalletFacade.init() in 2.x stalls on standalone dev nodes
   In SDK 2.x, WalletFacade.init() subscribes to runtime version events that the
   standalone node closes early. The wallet hangs in 'syncing' state forever
   with no error.
   → fix: Either (a) develop against preprod once past hello-world, or
     (b) upgrade to wallet-sdk-facade@4.0.0 which reverted to constructor +
     .start() pattern.
✓ node: midnightntwrk/midnight-node:0.21.0
✓ indexer: midnightntwrk/indexer-standalone:4.0.0-rc.4
✓ proof-server: midnightntwrk/proof-server:7.0.0
✓ midnight-node:0.21.0 matches SDK track

summary: 4 ok  3 warn  0 error  0 info
Status: workable, but warnings deserve a look.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 6-hour incident from March is now a 30-second output.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;The tool is structurally trivial. Three scanners, one diagnosis engine, one report formatter. Total surface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;midnight-doctor/
├── bin/midnight-doctor.js   # CLI entry, arg parsing, exit codes
├── lib/
│   ├── scan-package.js      # reads package.json + walks node_modules
│   ├── scan-docker.js       # runs `docker ps`, parses image tags
│   ├── scan-config.js       # reads .npmrc, indexer.yml
│   ├── diagnose.js          # cross-references findings with matrix
│   ├── report.js            # ANSI-colored pretty output
│   └── index.js             # public API
├── data/compatibility-matrix.json   # the source of truth
└── test/diagnose.test.js    # node:test, no framework
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The compatibility matrix is the product
&lt;/h3&gt;

&lt;p&gt;The ~700 lines of code are scaffolding. The actual &lt;em&gt;value&lt;/em&gt; is the JSON document at &lt;code&gt;data/compatibility-matrix.json&lt;/code&gt;. It encodes three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Tracks&lt;/strong&gt; — known-good combinations of SDK + Docker images, labeled (&lt;code&gt;current&lt;/code&gt;, &lt;code&gt;preprod-3x&lt;/code&gt;, &lt;code&gt;preprod-1x&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Known issues&lt;/strong&gt; — specific bugs keyed by package version range or config pattern&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-cutting checks&lt;/strong&gt; — rules that fire when two scanners disagree (e.g., node tag vs SDK track)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tracks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"current"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Current (latest npm + preprod compatible)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.21.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"indexer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4.0.0-rc.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"proofServer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"packages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"@midnight-ntwrk/wallet-sdk"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"@midnight-ntwrk/wallet-sdk-facade"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"@midnight-ntwrk/compact-runtime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.15.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"knownIssues"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"facade-2x-init-bug"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"warn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"package-version"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                 &lt;/span&gt;&lt;span class="nl"&gt;"package"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@midnight-ntwrk/wallet-sdk-facade"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                 &lt;/span&gt;&lt;span class="nl"&gt;"range"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.x"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WalletFacade.init() in 2.x stalls on standalone dev nodes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"fix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Either develop against preprod, or upgrade to facade 4.0.0..."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file is the same information that lives, scattered, across Discord pinned messages and individual developers' heads. Centralizing it in a JSON document accomplishes three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Source of truth&lt;/strong&gt; — there's now a place to point people instead of "search the channel"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Versioned&lt;/strong&gt; — the file has a &lt;code&gt;verifiedAt: "2026-04-27"&lt;/code&gt; field; staleness is detectable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Executable&lt;/strong&gt; — code can act on it; humans can read it&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The scanners are deliberately dumb
&lt;/h3&gt;

&lt;p&gt;Each scanner is one file, one responsibility, ~50 lines.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/scan-docker.js (excerpt)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;scanDocker&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;findings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;dockerAvailable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;docker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;version&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--format&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{{.Server.Version}}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="nx"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dockerAvailable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;stdout&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;docker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ps&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--format&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{{.Image}}|{{.Names}}|{{.Status}}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;|&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;imageName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;latest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;KNOWN_IMAGES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;imageName&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;KNOWN_IMAGES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;imageName&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;imageName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No Docker SDK dependency, no parsing library — just &lt;code&gt;child_process.execFile&lt;/code&gt; and &lt;code&gt;String.prototype.split&lt;/code&gt;. If Docker isn't installed, the scanner returns &lt;code&gt;{ dockerAvailable: false }&lt;/code&gt; and the diagnosis engine emits an &lt;code&gt;info&lt;/code&gt; diagnostic instead of crashing.&lt;/p&gt;

&lt;p&gt;The package scanner is similar — walks &lt;code&gt;node_modules&lt;/code&gt; recursively (capped at 5000 directories so monorepos don't hang), records every &lt;code&gt;@midnight-ntwrk/*&lt;/code&gt; it finds and what version. Duplicates fall out as a side effect: if the same package name has more than one version in the array, it's a duplicate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allInstances&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;findAllInstances&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;projectDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MIDNIGHT_NS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;allInstances&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unique&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;duplicates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three lines. That's the entire duplicate-detection logic. The remaining ~50 lines of the scanner are filesystem traversal — boring, mechanical, but it works on every Node project regardless of package manager (npm, pnpm, yarn, bun all create &lt;code&gt;node_modules&lt;/code&gt; directories with the same shape).&lt;/p&gt;

&lt;h3&gt;
  
  
  The diagnosis engine is glorified table joins
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;lib/diagnose.js&lt;/code&gt; takes the three scan results plus the matrix and emits diagnostics. The pattern repeats per check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;diagnoseCrossCutting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;docker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;facadeVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;installed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@midnight-ntwrk/wallet-sdk-facade&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nodeContainer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;docker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;facadeVersion&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;nodeContainer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;track&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tracks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;detectTrack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;facadeVersion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;nodeContainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node-track-mismatch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`midnight-node:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;nodeContainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; doesn't match SDK track (expects &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Mixing causes silent sync failures with no error.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;fix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Either update the node container to &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, or align SDK to a track that matches your node version.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node-track-match&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`midnight-node:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;nodeContainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; matches SDK track`&lt;/span&gt; &lt;span class="p"&gt;}];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire body of the cross-cut check that would have saved my 6-hour March incident. &lt;strong&gt;Twelve lines. The information was there the whole time.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The full diagnosis file is ~200 lines. Most of it is the same shape: pick two facts from the scans, compare against the matrix, emit a diagnostic. It's deliberately boring code.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this isn't
&lt;/h2&gt;

&lt;p&gt;Scope discipline is half the value of a tool like this. Things &lt;code&gt;midnight-doctor&lt;/code&gt; deliberately doesn't do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It doesn't auto-fix.&lt;/strong&gt; No &lt;code&gt;--fix&lt;/code&gt; flag yet. The cost of a wrong auto-fix in this domain (bricked node_modules, lost work) outweighs the convenience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It doesn't curl health endpoints.&lt;/strong&gt; It checks Docker container &lt;em&gt;presence&lt;/em&gt;, not &lt;em&gt;health&lt;/em&gt;. A separate health-check script (&lt;code&gt;midnight-health-check.sh&lt;/code&gt;) covers that already in my infra repo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It doesn't probe the chain.&lt;/strong&gt; No &lt;code&gt;getBlockNumber()&lt;/code&gt;, no balance lookup. Those depend on a working SDK, which is what doctor is meant to validate &lt;em&gt;before&lt;/em&gt; you try to use it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It isn't a package manager.&lt;/strong&gt; It won't run &lt;code&gt;npm dedupe&lt;/code&gt; for you. It tells you to run it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It doesn't write code.&lt;/strong&gt; It doesn't generate scaffolds, codemods, or migrations. That's a different tool (&lt;code&gt;create-midnight-app&lt;/code&gt;, eventually).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each non-feature is a deliberate choice to keep the surface small enough that the tool stays correct.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd build next, if there's appetite
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;midnight-doctor&lt;/code&gt; is the smallest useful version. The compatibility matrix as data unlocks several adjacent tools:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;create-midnight-app&lt;/code&gt;&lt;/strong&gt; — scaffold a project where &lt;code&gt;midnight-doctor&lt;/code&gt; is wired into &lt;code&gt;npm install&lt;/code&gt; lifecycle. The matrix becomes the default versions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A codemod for the 2.x → 4.x facade migration.&lt;/strong&gt; The API change is mechanical: &lt;code&gt;WalletFacade.init({...})&lt;/code&gt; → &lt;code&gt;new WalletFacade(...) + .start(...)&lt;/code&gt;. AST transform.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compactc compiler version check.&lt;/strong&gt; Today it's via shell-out. With a parser, doctor could read your &lt;code&gt;.compact&lt;/code&gt; source and say "this uses syntax X, requires compactc ≥ Y."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remote-fetched matrix.&lt;/strong&gt; Right now the matrix is bundled. A nightly job at &lt;code&gt;https://compatibility.midnight.network/matrix.json&lt;/code&gt; would let users not need to bump the doctor itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Editor integration.&lt;/strong&gt; A VSCode extension that runs doctor on save and surfaces diagnostics in the problems panel. The CLI is for CI; the editor is for dev loop.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first one is the highest leverage by far — it short-circuits the entire onboarding problem. But it's a much bigger project. Doctor is a Trojan horse for the matrix; once the matrix exists and people accept it as canonical, the rest is plumbing.&lt;/p&gt;




&lt;h2&gt;
  
  
  The broader argument: tools beat docs
&lt;/h2&gt;

&lt;p&gt;Documentation rots. A README written today describes a system that, six weeks from now, has moved two majors. Anyone who builds in a fast-moving ecosystem has lived this: the official doc says "use SDK 1.0", the npm &lt;code&gt;latest&lt;/code&gt; tag says 4.0, the GitHub README says 3.0, and the Discord pinned message says "we know, sorry, the docs are stale." None of those sources is wrong. They were all true at some point. The problem is they don't update each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Executable knowledge updates itself.&lt;/strong&gt; A JSON file with a &lt;code&gt;verifiedAt&lt;/code&gt; date that's three weeks old is &lt;em&gt;visibly&lt;/em&gt; stale. A doc that's three weeks old looks identical to a doc that's three days old. Tools force the question "is this still true?" in a way that prose doesn't.&lt;/p&gt;

&lt;p&gt;This isn't unique to Midnight. Every fast-moving stack reaches the point where the gap between "what the docs claim" and "what actually works" is wide enough to swallow new developers. The fix is the same: take the lookup table out of human heads, encode it as data, ship it as a tool that runs in seconds.&lt;/p&gt;

&lt;p&gt;The Midnight protocol team doesn't need to build &lt;code&gt;midnight-doctor&lt;/code&gt;. They could. Anyone could. The information already exists — in pinned messages, in CHANGELOG.md files, in the heads of the half-dozen people on Discord who answer the same question every week. &lt;strong&gt;The work isn't producing the information. It's transcribing it once, into a format machines can act on, and committing to keeping it current.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's what this ~700-line tool does. It's not clever. It's not hard. It's just nobody had done it.&lt;/p&gt;

&lt;p&gt;If you've spent hours on a Midnight silent failure, please run &lt;code&gt;npx midnight-doctor&lt;/code&gt; against your project before your next debugging session. If you find a bug doctor missed, &lt;a href="https://github.com/fredericosanntana/midnight-doctor/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt; — the matrix is the artifact, the code is just glue.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/fredericosanntana/midnight-doctor" rel="noopener noreferrer"&gt;github.com/fredericosanntana/midnight-doctor&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Install:&lt;/strong&gt; &lt;code&gt;npm install -g midnight-doctor&lt;/code&gt; or &lt;code&gt;npx midnight-doctor&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;License:&lt;/strong&gt; MIT&lt;/p&gt;

&lt;p&gt;If this was useful, the people who actually maintain Midnight (&lt;a href="https://twitter.com/MidnightNtwrk" rel="noopener noreferrer"&gt;@MidnightNtwrk&lt;/a&gt;) deserve the visibility — half this matrix came from their Discord answers. The other half came from getting burned. Both contributions are essential.&lt;/p&gt;




&lt;h2&gt;
  
  
  Appendix: the full check list
&lt;/h2&gt;

&lt;p&gt;For reference, every check &lt;code&gt;midnight-doctor&lt;/code&gt; currently runs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Package scanner:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✓ Detect SDK track (&lt;code&gt;current&lt;/code&gt;, &lt;code&gt;preprod-3x&lt;/code&gt;, &lt;code&gt;preprod-1x&lt;/code&gt;) from &lt;code&gt;wallet-sdk-facade&lt;/code&gt; major&lt;/li&gt;
&lt;li&gt;✓ Flag deprecated tracks with migration guidance&lt;/li&gt;
&lt;li&gt;✓ Detect major-version mismatch across &lt;code&gt;wallet-sdk-*&lt;/code&gt; subpackages&lt;/li&gt;
&lt;li&gt;✓ Detect duplicate &lt;code&gt;@midnight-ntwrk/ledger-v7&lt;/code&gt; in &lt;code&gt;node_modules&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;✓ Detect duplicate &lt;code&gt;@midnight-ntwrk/compact-runtime&lt;/code&gt; in &lt;code&gt;node_modules&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;✓ Flag &lt;code&gt;wallet-sdk-facade@2.x&lt;/code&gt; standalone init bug&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Docker scanner:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✓ Detect running &lt;code&gt;midnight-node&lt;/code&gt;, &lt;code&gt;indexer-standalone&lt;/code&gt;, &lt;code&gt;proof-server&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;✓ Cross-reference node tag with SDK track&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Config scanner:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✓ Flag &lt;code&gt;.npmrc&lt;/code&gt; with bogus &lt;code&gt;npm.midnight.network&lt;/code&gt; registry&lt;/li&gt;
&lt;li&gt;✓ Flag &lt;code&gt;indexer.yml&lt;/code&gt; missing &lt;code&gt;subscription:&lt;/code&gt; block&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cross-cutting:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✓ Node tag ↔ SDK track consistency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Roadmap:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;☐ Compact compiler version vs runtime&lt;/li&gt;
&lt;li&gt;☐ Health probes (RPC, GraphQL, proof endpoints)&lt;/li&gt;
&lt;li&gt;☐ Monorepo workspace iteration&lt;/li&gt;
&lt;li&gt;☐ &lt;code&gt;--fix&lt;/code&gt; mode for safe auto-corrections&lt;/li&gt;
&lt;li&gt;☐ Remote-fetched matrix&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total checks today: 11. Adding more is one PR each.&lt;/p&gt;

</description>
      <category>midnightchallenge</category>
      <category>web3</category>
    </item>
    <item>
      <title>How a Blockchain Transaction Works: From Start to Final Block</title>
      <dc:creator>Fred Santana</dc:creator>
      <pubDate>Tue, 24 Mar 2026 15:16:32 +0000</pubDate>
      <link>https://dev.to/fredsanntana/how-a-blockchain-transaction-works-from-start-to-final-block-44e7</link>
      <guid>https://dev.to/fredsanntana/how-a-blockchain-transaction-works-from-start-to-final-block-44e7</guid>
      <description>&lt;p&gt;Why should a compliance professional care about this?&lt;/p&gt;

&lt;p&gt;I spend a good part of my days thinking about how to protect personal data. LGPD (Brazilian General Data Protection Law), privacy policies, legal bases — that's my world. When I started studying blockchain seriously, I realized that it wasn't enough to understand what the technology does. I needed to understand how information enters this network and why, once it's in, it's practically impossible to remove it.&lt;/p&gt;

&lt;p&gt;This "practically impossible" is what keeps those who work with compliance awake at night. But before entering this debate — which will be the heart of this 30-day campaign — we need to understand the basic mechanics. How does a transaction originate, travel through the network, and become permanent?&lt;/p&gt;

&lt;p&gt;I'll explain without a single line of code. I promise.&lt;/p&gt;

&lt;p&gt;The analogy of the digital notary office&lt;br&gt;
Think of a notary office. You go there, sign a document, the notary recognizes your signature, registers everything in a book, and stamps it with the date and time. From that moment on, that record officially exists.&lt;/p&gt;

&lt;p&gt;A blockchain transaction follows a similar logic — only without the notary. There is no person or company at the center deciding what is valid. The entire network plays this role. And the "registry book" isn't kept in just one place: it's copied on thousands of computers around the world.&lt;/p&gt;

&lt;p&gt;Does that seem excessive? It's precisely this excessiveness that makes things secure. And it's this same excessiveness that creates the challenge for the LGPD (Brazilian General Data Protection Law).&lt;/p&gt;

&lt;p&gt;The five stages of a transaction&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creation — "I want to register something"
It all starts with an intention. Someone wants to send a value, register data, or perform an action on the network. This intention becomes a "transaction" — a package of information that basically says: who is sending, to whom, and what.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Think of it like filling out a form at the registry office. You haven't registered anything yet — you've only written what you want to do.&lt;/p&gt;

&lt;p&gt;Connection with compliance: even at this stage, if the data being registered is personal, the LGPD comes into play. Article 7 requires a legal basis for any processing of personal data. The simple creation of a transaction containing a person's data is already "processing" under the law.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Signature — "I confirm that it's me"
Before submitting the form, you need to prove that it's you. In a physical notary's office, this would be your notarized signature. On the blockchain, there's a mechanism called a digital signature.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It works like this: you have two keys. One is like your safe's number (public — anyone can see it). The other is like the combination that opens the safe (private — only you know it). When you "sign" a transaction, you're using your private key to create a unique mark that anyone can verify using your public key, but that no one can forge.&lt;/p&gt;

&lt;p&gt;It's as if your signature at the notary's office were impossible to copy. Literally impossible — not difficult, impossible.&lt;/p&gt;

&lt;p&gt;Connection to compliance: the public key, although it doesn't contain your name, can be traced back to you under certain conditions. Article 12, §2 of the LGPD (Brazilian General Data Protection Law) states that data that can be used to identify someone, even indirectly, is personal data. A blockchain public key can fit this definition — and this is one of the most important debates in the field.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Propagation — "Spreading the word"
After being signed, the transaction is sent to the network. Imagine you handed your signed form not to a notary's office, but to a public square full of people. Each person who receives the information passes it on to others.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In a few seconds, thousands of computers (called "nodes" of the network) already have a copy of your transaction. It is not yet confirmed — it is in a "waiting room" called a mempool, waiting to be processed.&lt;/p&gt;

&lt;p&gt;It's as if the notary's office had a queue: your document has been received, but it hasn't been stamped yet.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Validation — "Checking if everything is correct"
This is where the notary's office replaces the notary. Specialized computers (in the case of Bitcoin, called miners; in other networks, validators) take transactions from the waiting room and check:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Is the digital signature valid? Does the sender actually have what they claim to have?&lt;/p&gt;

&lt;p&gt;Does the transaction follow all the network rules?&lt;/p&gt;

&lt;p&gt;If something is wrong—a forged signature, insufficient funds, any irregularity—the transaction is rejected. No appeal, no "workaround." The rules are the same for everyone.&lt;/p&gt;

&lt;p&gt;Imagine a group of independent accountants, each checking the same spreadsheet. If the majority agree that the accounts balance, the transaction is approved.&lt;/p&gt;

&lt;p&gt;Compliance connection: this decentralized validation is fascinating from the perspective of Article 46 of the LGPD (Brazilian General Data Protection Law), which requires "security measures capable of protecting personal data." Distributed consensus validation is, technically, one of the most robust ways to guarantee data integrity that we have ever invented. But who is the "controller" responsible for this validation? The entire network? Nobody? That's another open question.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Inclusion in the Block — "Stamped and Archived Forever"
Approved transactions are grouped into a block — think of it as a page in a notary's ledger. This page receives a unique stamp (called a hash) that functions like a fingerprint: if someone tries to alter even a comma on the page, the stamp changes completely, and everyone notices the fraud.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But the crucial detail is this: each new page contains the stamp of the previous page. This creates a chain — hence the name blockchain (chain of blocks). To alter an old transaction, you would need to redo all subsequent pages on thousands of computers simultaneously. In practice, this is unfeasible.&lt;/p&gt;

&lt;p&gt;The transaction is now permanent. It's in the ledger. The ledger is in thousands of places. No one can tear the page out.&lt;/p&gt;

&lt;p&gt;Connection with compliance: and this is exactly where the paradox lies. Article 18, item VI of the LGPD (Brazilian General Data Protection Law) guarantees the data subject the right to the deletion of personal data. But how do you delete something from a book that, by design, was made to be unalterable? This conflict between technical immutability and the legal right to be forgotten is the theme we will explore throughout this campaign.&lt;/p&gt;

&lt;p&gt;What I learned building DPO2U&lt;br&gt;
When I started developing DPO2U — a tool that attempts to reconcile blockchain with data protection — I thought the biggest challenge would be technical. Code, contracts, infrastructure.&lt;/p&gt;

&lt;p&gt;I was wrong. The biggest challenge is conceptual. It's understanding that blockchain wasn't designed with privacy in mind. It was designed with transparency and immutability in mind. And the LGPD (Brazilian General Data Protection Law) wasn't designed with blockchain in mind. It was designed with traditional databases in mind that can be edited and deleted.&lt;/p&gt;

&lt;p&gt;These two logics are on a collision course. And understanding the mechanics of a transaction — how it is created, validated, and becomes permanent — is the first step in understanding why this collision is so difficult to resolve.&lt;/p&gt;

&lt;p&gt;Where are we going tomorrow?&lt;br&gt;
Now that you know how a transaction becomes irreversible, the natural question is: what if I need to delete this data? If the LGPD (Brazilian General Data Protection Law) gives me the right to request the deletion of my information, how does this work in a system that was built to never forget anything?&lt;/p&gt;

&lt;p&gt;On Day 3, we'll dive headfirst into this conflict: the immutability of the blockchain versus the right to be forgotten. This is where the privacy paradox gets really interesting.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>What is Blockchain: A Technical Guide for Compliance Professionals</title>
      <dc:creator>Fred Santana</dc:creator>
      <pubDate>Mon, 23 Mar 2026 14:11:27 +0000</pubDate>
      <link>https://dev.to/fredsanntana/what-is-blockchain-a-technical-guide-for-compliance-professionals-dln</link>
      <guid>https://dev.to/fredsanntana/what-is-blockchain-a-technical-guide-for-compliance-professionals-dln</guid>
      <description>&lt;p&gt;Why a DPO Needs to Understand Blockchain&lt;/p&gt;

&lt;p&gt;I’m Fred, founder of DPO2U. In recent months, I’ve been publicly building an on-chain compliance ecosystem, and I’ve encountered a problem that few admit exists: the technology that promises perfect auditability can violate the very law it’s supposed to protect.&lt;/p&gt;

&lt;p&gt;This conflict has a name in academic literature. De Filippi and Wright, in Blockchain and the Law (2018), already warned that immutability would create direct tensions with the right to data deletion. In my Zettelkasten, I registered this as the Paradox of Immutability (ZK-20260125-013): the virtue of blockchain for integrity is its vice when personal data needs to be deleted.&lt;/p&gt;

&lt;p&gt;This article is Day 1 of 30 in the “The Privacy Paradox” campaign. My goal: to explain, without unnecessary jargon, what you need to know about blockchain to make informed decisions. Not to become a programmer, but to avoid being deceived by those who sell magical solutions.&lt;/p&gt;

&lt;p&gt;The Promise: Why Blockchain Matters for Compliance&lt;/p&gt;

&lt;p&gt;Imagine your company needs to prove it obtained consent from a data subject on a specific date. Today, this proof is usually in an internal database controlled by the company itself. If there is litigation, the other party might question: “who guarantees that this record hasn’t been altered?”&lt;/p&gt;

&lt;p&gt;In the DPO2U Whitepaper, I documented the vulnerabilities of the current system: privacy policies in editable PDFs without a trace, consent records in deletable spreadsheets, DPIAs with manual versioning. Which version is valid?&lt;/p&gt;

&lt;p&gt;With blockchain, the consent record would be in an immutable ledger verifiable by third parties. No one would need to trust the company’s word. Think about the applications:&lt;/p&gt;

&lt;p&gt;Consent record with an inviolable timestamp, an audit trail that no system administrator can tamper with, proof of compliance verifiable by regulators without depending on the company’s good faith.&lt;/p&gt;

&lt;p&gt;It’s like replacing that Excel file that “proves” compliance with a digital notary record that no one controls alone.&lt;/p&gt;

&lt;p&gt;Public vs. Private Blockchain: The Distinction That Matters&lt;/p&gt;

&lt;p&gt;Not all blockchains work the same way, and this distinction is crucial for compliance.&lt;/p&gt;

&lt;p&gt;A public blockchain functions like a mural in a public square. Anyone can read all the records. Bitcoin and Ethereum are examples. In them, recording a CPF (Brazilian tax identification number) or email address would be like writing personal data on a billboard, visible forever to everyone.&lt;/p&gt;

&lt;p&gt;A private (or permissioned) blockchain functions more like the internal system of a consortium of companies. Only authorized participants can read and write. There is more control, but less decentralization.&lt;/p&gt;

&lt;p&gt;For compliance professionals, the key question facing any blockchain solution is: who can read what is recorded? If the answer is “anyone on the internet,” Article 46 of the LGPD (Brazilian General Data Protection Law), which requires technical measures to protect personal data, is at risk.&lt;/p&gt;

&lt;p&gt;Five Questions Every DPO Should Ask Before Accepting a Blockchain Solution&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Is personal data on-chain or off-chain? If the answer is on-chain, immediately question compliance with Articles 16 and 18 of the LGPD (Brazilian General Data Protection Law).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Is the blockchain public or permissioned? This defines who has access to the records and directly impacts Article 46 (security measures).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Is there a deletion or anonymization mechanism? Immutable blockchain + personal data = regulatory risk. Ask how the provider intends to handle deletion requests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Who is the data controller? In decentralized networks, the chain of responsibility foreseen in Article 5 of the LGPD becomes unclear. Someone needs to be responsible for compliance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What is the legal basis for processing? Article 7 of the LGPD requires a legal basis for any processing. “It’s on the blockchain” is not a legal basis.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Day 1 of 30 — “The Privacy Paradox” Campaign. References: De Filippi, P., &amp;amp; Wright, A. (2018). Blockchain and the Law. Harvard University Press. | LGPD, Law 13.709/2018. Next: Immutability vs. Right to be Forgotten&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
