<?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: holistis</title>
    <description>The latest articles on DEV Community by holistis (@holistis).</description>
    <link>https://dev.to/holistis</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3940045%2Fcc7c530b-20f7-472b-892a-d90a4da14eb5.png</url>
      <title>DEV Community: holistis</title>
      <link>https://dev.to/holistis</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/holistis"/>
    <language>en</language>
    <item>
      <title>I built a judge that argues with itself before you submit a bug bounty finding</title>
      <dc:creator>holistis</dc:creator>
      <pubDate>Tue, 11 Aug 2026 12:58:14 +0000</pubDate>
      <link>https://dev.to/holistis/i-built-a-judge-that-argues-with-itself-before-you-submit-a-bug-bounty-finding-4fa0</link>
      <guid>https://dev.to/holistis/i-built-a-judge-that-argues-with-itself-before-you-submit-a-bug-bounty-finding-4fa0</guid>
      <description>&lt;h1&gt;
  
  
  I built a judge that argues with itself before you submit a bug bounty finding
&lt;/h1&gt;

&lt;p&gt;A few days ago I published data on &lt;a href="https://dev.to/holistis/what-actually-gets-paid-in-smart-contract-audit-contests-583o"&gt;what actually gets paid in smart contract audit contests&lt;/a&gt;. The short version: submitting everything you find is a bad strategy. Rejected and duplicate submissions cost you time, reputation, and sometimes a stake. Being selective about what you actually submit is worth more than volume.&lt;/p&gt;

&lt;p&gt;So I built the tool I wish existed for that: &lt;a href="https://github.com/holistis/al-mizaan-judge" rel="noopener noreferrer"&gt;Al-Mizaan Judge&lt;/a&gt;, a CLI that takes your finding and puts it through a strict, adversarial review before you spend a real Sherlock, Immunefi, or Cantina submission on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Two layers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mechanical gates&lt;/strong&gt; run first, free, no API call. They catch the cheap, obvious kills: a trusted actor (owner/admin/governance) as the sole attacker, a deployer misconfiguration with no external attacker, an oracle-manipulation finding that never proves economic viability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adversarial debate&lt;/strong&gt; runs for anything that survives that. A Defender agent argues the code is safe. An Attacker agent tries to break that defense. A Judge decides, walking through scope, reachability, threat model, invariant breach, protocol intent, and dollar impact in that order. If it goes three rounds, the final round restricts the Judge to only code citations that were mechanically checked against the code you actually provided. No rhetoric, only what's in the code. Doubt always defaults to KILL.&lt;/p&gt;

&lt;h2&gt;
  
  
  A real example
&lt;/h2&gt;

&lt;p&gt;I ran it against a textbook reentrancy bug:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function withdraw(uint256 amount) external {
    require(balances[msg.sender] &amp;gt;= amount, "insufficient balance");
    (bool ok, ) = msg.sender.call{value: amount}("");
    require(ok, "transfer failed");
    balances[msg.sender] -= amount;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verdict: &lt;strong&gt;SUBMIT CANDIDATE&lt;/strong&gt;, 90 to 98% confidence across all three rounds.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Vault.sol code is in scope, and withdraw() performs a classic checks-effects-interactions violation: it sends ETH via a low-level call before decrementing balances[msg.sender], with no reentrancy guard present. This is reachable by any untrusted external account, directly breaching the solvency/accounting invariant.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then I ran it against a finding that sounds scary but isn't: an oracle that falls back to a manipulable TWAP during Chainlink downtime.&lt;/p&gt;

&lt;p&gt;Verdict: &lt;strong&gt;LIKELY REJECTED&lt;/strong&gt;, one round, 72% confidence.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Fails Gate 5 (Impact): no dollar/TVL quantification from either side. Additionally, the finding's trigger condition is fundamentally Chainlink-staleness-driven fallback behavior, which Sherlock's SJIP-21 rule marks invalid as a primary finding.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That second one matters. It didn't just say "seems fine," it cited the actual platform rule that kills this specific pattern on Sherlock. That's the difference between a generic "is this a bug" prompt and something that actually knows how contest judging works.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it deliberately doesn't do
&lt;/h2&gt;

&lt;p&gt;No code execution, no Foundry builds, no repo scanning, no hosting. You run it locally with your own Anthropic API key. Your code never leaves your machine except to the API you're already paying for directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk-ant-...
npx al-mizaan-judge your-finding.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repo and full format docs: &lt;a href="https://github.com/holistis/al-mizaan-judge" rel="noopener noreferrer"&gt;https://github.com/holistis/al-mizaan-judge&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One honesty note, because I'd rather say this myself than have someone find it: the mechanical gates encode Sherlock's own published judging policy, not our own statistics. We haven't yet statistically validated verdicts against a large, representative outcome dataset, that corpus work is still in progress and I'll write about it when it's real. Until then, treat this as a strict second opinion grounded in real audit methodology, not a proven hit rate.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>opensource</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>What actually gets paid in smart contract audit contests</title>
      <dc:creator>holistis</dc:creator>
      <pubDate>Mon, 10 Aug 2026 15:33:56 +0000</pubDate>
      <link>https://dev.to/holistis/what-actually-gets-paid-in-smart-contract-audit-contests-583o</link>
      <guid>https://dev.to/holistis/what-actually-gets-paid-in-smart-contract-audit-contests-583o</guid>
      <description>&lt;p&gt;I run a smart contract audit pipeline, and I keep a labeled record of what judges actually reward. I pulled 20,720 findings across 81 Sherlock contests and counted only the ones that carried a real Reward label. The thing that turned out to matter most was not the one I expected, and it corrected something I believed going in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Most valid findings do get paid. The rate just swings wildly.
&lt;/h2&gt;

&lt;p&gt;Across all 81 contests, 66 percent of submissions were rewarded. But that average hides the real story. Per contest, the paid rate ran from near 0 percent to near 99 percent, with a median of 60 percent. Twenty-three of the contests paid less than a quarter of submissions. Thirty-eight paid more than three quarters.&lt;/p&gt;

&lt;p&gt;So the single biggest lever on your odds is not your skill or the bug you find. It is which contest you enter. Same researcher, same ability, wildly different outcome depending on the codebase, the field of competitors, and how strict the judging was.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug type barely matters. This was the surprise.
&lt;/h2&gt;

&lt;p&gt;I expected the prestigious bugs, reentrancy and oracle manipulation, to separate cleanly from the boring ones. At 20,000 findings, they do not. Every major pattern pays inside a narrow band.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Paid rate&lt;/th&gt;
&lt;th&gt;Paid / submitted&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;liquidation&lt;/td&gt;
&lt;td&gt;77%&lt;/td&gt;
&lt;td&gt;1624 / 2118&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fee miscalculation&lt;/td&gt;
&lt;td&gt;76%&lt;/td&gt;
&lt;td&gt;1601 / 2105&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;oracle manipulation&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;1949 / 2586&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mev / slippage&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;1053 / 1410&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flash loan&lt;/td&gt;
&lt;td&gt;71%&lt;/td&gt;
&lt;td&gt;330 / 465&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rounding&lt;/td&gt;
&lt;td&gt;70%&lt;/td&gt;
&lt;td&gt;1480 / 2104&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dos / griefing&lt;/td&gt;
&lt;td&gt;70%&lt;/td&gt;
&lt;td&gt;1828 / 2610&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;access control&lt;/td&gt;
&lt;td&gt;68%&lt;/td&gt;
&lt;td&gt;1241 / 1812&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;reentrancy&lt;/td&gt;
&lt;td&gt;67%&lt;/td&gt;
&lt;td&gt;548 / 820&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;overflow&lt;/td&gt;
&lt;td&gt;66%&lt;/td&gt;
&lt;td&gt;656 / 990&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;trusted actor&lt;/td&gt;
&lt;td&gt;65%&lt;/td&gt;
&lt;td&gt;4547 / 6948&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;staleness&lt;/td&gt;
&lt;td&gt;65%&lt;/td&gt;
&lt;td&gt;2983 / 4585&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The whole spread from top to bottom is twelve points. The pattern you find is close to a rounding error next to the contest you picked. If you are choosing what to hunt based on which bug class feels most valuable, you are optimizing the wrong variable.&lt;/p&gt;

&lt;p&gt;One category is worth a note for a different reason. Trusted actor is by far the largest, 6,948 submissions, nearly a third of everything. Most of it is people arguing that an owner or admin could misbehave. It pays about the same as the rest here, but in my own pipeline it is the category I reject most often, because when the only attacker is an already-trusted role, most programs do not treat it as a finding. High volume, ordinary hit rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Claiming High barely helps, and it rarely survives.
&lt;/h2&gt;

&lt;p&gt;Findings submitted as High got paid 74 percent of the time. Findings submitted as Medium got paid 65 percent. A real but modest gap.&lt;/p&gt;

&lt;p&gt;The bigger tell is what happens to the label. Only 10 percent of findings submitted as High actually ended up rated High. The other 90 percent were downgraded or dropped. The market over claims severity by a wide margin, and the judges cut most of it back down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Even when you win, sometimes you split the pot.
&lt;/h2&gt;

&lt;p&gt;17 percent of the findings that got paid were duplicates. Real bugs, but someone else reported the same thing, so the reward was shared. It is not the majority, but it is common enough that finding a bug others will also find is worth less than finding one they will miss.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this is, and what it is not
&lt;/h2&gt;

&lt;p&gt;The sample is 20,720 findings across 81 fully crawled Sherlock contests from 2024. Every one of these contests has its complete finding list, not a partial crawl. It is one platform and one year, so treat it as a large slice, not the whole market.&lt;/p&gt;

&lt;p&gt;I count a finding as paid only if it carries the Reward label in the public judging result. That counts each individual submission, including duplicate submitters who shared a payout. Confirmed but unrewarded, disputed, and rejected all count as not paid.&lt;/p&gt;

&lt;p&gt;Each finding is tagged by bug class from its title and judge notes. Reasonable people would tag a few differently. The direction of the numbers is what matters, not the second decimal.&lt;/p&gt;

&lt;p&gt;One honest note on method, because it is the whole point. I first ran this on a small set of 10 contests and got a very different picture, a low overall pay rate and reentrancy on top. When I widened it to 81 contests, both of those flipped. The small sample happened to be full of low paying contests. That is exactly why the contest you pick matters more than the pattern, and it is a good reminder that a clean looking number from a small sample can point the wrong way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The skill all of this rewards is judgment: before you spend a submission on a finding, knowing whether it will actually be rewarded, or whether it is a dup, a known issue, or an argument the judges will wave away. I built the judge I use for exactly that, and it is live and free to run:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx al-mizaan-judge&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It runs your finding through a strict 7-gate validity check and tells you where it would fail and why, before you spend the submission. Package: &lt;a href="https://www.npmjs.com/package/al-mizaan-judge" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/al-mizaan-judge&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the verdict surprises you, or you just want the full data behind this, leave a comment or reach out. I read everything.&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>crypto</category>
      <category>security</category>
      <category>web3</category>
    </item>
    <item>
      <title>Your AI-built app is impressive. It is just not yours yet.</title>
      <dc:creator>holistis</dc:creator>
      <pubDate>Sun, 09 Aug 2026 16:37:52 +0000</pubDate>
      <link>https://dev.to/holistis/your-ai-built-app-is-impressive-it-is-just-not-yours-yet-9b3</link>
      <guid>https://dev.to/holistis/your-ai-built-app-is-impressive-it-is-just-not-yours-yet-9b3</guid>
      <description>&lt;p&gt;A pattern I keep seeing: someone builds a real, working product in a weekend using Lovable, Bolt, Replit Agent, or Manus. It looks great, it works, people sign up. Then they hit the wall nobody warned them about.&lt;/p&gt;

&lt;p&gt;The code doesn't live in a repo they control. It lives inside the platform. Want to add a developer to the team who isn't inside that tool's workflow? Want to self-host, or move to a different host? Want to just &lt;code&gt;git clone&lt;/code&gt; it and work on it in your own editor like a normal codebase? Often you can't, not cleanly. You're renting your own product.&lt;/p&gt;

&lt;p&gt;That's not a knock on those tools — they're genuinely good at getting from zero to a working prototype fast. But "fast to build" and "yours to keep" turned out to be two different problems, and most of these platforms only solve the first one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "owning it" actually requires
&lt;/h2&gt;

&lt;p&gt;Three things, mainly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A real GitHub repo&lt;/strong&gt;, not an export button that dumps a zip once and calls it done.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A history that makes sense&lt;/strong&gt; — commits, not a single "initial state" blob you can't diff against.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A way to keep improving it&lt;/strong&gt; that doesn't require going back through the platform that built it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I built: MergeFix
&lt;/h2&gt;

&lt;p&gt;I built &lt;a href="https://mergefix.com" rel="noopener noreferrer"&gt;MergeFix&lt;/a&gt; to solve exactly this. The "Migrate &amp;amp; Own" flow takes whatever you've got — AI-built or not, existing GitHub repo or none at all — and puts it into a repo you fully own, no lock-in, no dependency on us to keep working. If you already have a GitHub repo, it's a normal PR-based handoff. If you don't (which is the common case coming out of an AI builder), it crawls your live site and bootstraps a real repo from it, first commit onward.&lt;/p&gt;

&lt;p&gt;Once it's in your repo, the same underlying engine can keep working for you: continuous audits for SEO, performance, accessibility and security, shipped as actual pull requests you review and merge — not a dashboard telling you things are wrong with no fix attached.&lt;/p&gt;

&lt;p&gt;The honest way to think about it: migration gets you ownership. The ongoing subscription (Pro/Scale) is optional maintenance on top, only if you want it — there's no obligation to keep paying us once your code is yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want to try Migrate &amp;amp; Own for free
&lt;/h2&gt;

&lt;p&gt;I'm running a genuinely time-boxed offer while this is fresh: code &lt;code&gt;MIGRATEFREE&lt;/code&gt; gives 100% off Migrate &amp;amp; Own, valid through August 16, capped at 25 uses. Not a fake countdown — those are the real limits.&lt;/p&gt;

&lt;p&gt;If you've built something with an AI app builder and it's starting to feel like it isn't quite yours, I'd like to hear about it either way — happy to look at your specific setup even if MergeFix isn't the right fit.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>A payment gateway for MCP servers, and the security bugs I found in my own code first</title>
      <dc:creator>holistis</dc:creator>
      <pubDate>Wed, 05 Aug 2026 07:08:12 +0000</pubDate>
      <link>https://dev.to/holistis/a-payment-gateway-for-mcp-servers-and-the-security-bugs-i-found-in-my-own-code-first-337</link>
      <guid>https://dev.to/holistis/a-payment-gateway-for-mcp-servers-and-the-security-bugs-i-found-in-my-own-code-first-337</guid>
      <description>&lt;p&gt;Fewer than 5% of MCP servers make money. I put a real payment gate in front of one and wrote down what actually broke.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem, with numbers
&lt;/h2&gt;

&lt;p&gt;There are 10,000+ Model Context Protocol (MCP) servers out there right now, with 97 million+ combined downloads. MCP is the emerging standard for how AI agents call tools — search, databases, APIs, whatever a server wants to expose. It's had a huge adoption wave.&lt;/p&gt;

&lt;p&gt;Fewer than 5% of the people running those servers earn anything from them.&lt;/p&gt;

&lt;p&gt;Not because nobody would pay. It's because "let people pay per call" means building, from scratch, on top of your actual product:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an HTTP 402 challenge/response flow&lt;/li&gt;
&lt;li&gt;signature verification for the payment authorization (real cryptographic recovery, not just "is there a signature-shaped string present")&lt;/li&gt;
&lt;li&gt;settlement against a payment facilitator&lt;/li&gt;
&lt;li&gt;replay protection (nobody should be able to reuse the same signed payment twice)&lt;/li&gt;
&lt;li&gt;revenue accounting per caller, per tool, per owner&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that has anything to do with what the MCP server actually &lt;em&gt;does&lt;/em&gt;. So most owners just... don't monetize, and eat the hosting cost, or don't run a business around it at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://wazir-x402.duckdns.org/mcp-paywall/" rel="noopener noreferrer"&gt;mcp-paywall&lt;/a&gt; is a proxy you drop in front of an existing MCP server, unmodified. It speaks &lt;a href="https://www.x402.org/" rel="noopener noreferrer"&gt;x402&lt;/a&gt; — the HTTP-402 + EIP-3009-signed-authorization pattern — and settles payments in USDC on Base.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI agent / client
      │  POST /mcp/:serverId  (MCP Streamable HTTP, JSON-RPC)
      ▼
┌─────────────────────────────────────────────────────────────┐
│ gateway-server.mjs                                            │
│                                                                 │
│  1. Peek at the JSON-RPC method BEFORE any MCP processing.    │
│     Not "tools/call", or the tool is free  →  pass through.   │
│                                                                 │
│  2. "tools/call" on a priced tool, no X-PAYMENT header        │
│         →  real HTTP 402 + accepts[] challenge                │
│                                                                 │
│  3. X-PAYMENT present  →  verify (local EIP-712 signature      │
│     recovery + expiry + payee + amount) + settle via           │
│     facilitator  →  invalid at ANY step  →  402                │
│                                                                 │
│  4. Valid  →  record the paid call + revenue split             │
│     →  forward the call, unmodified, to the REAL upstream      │
│     MCP server (stdio)                                         │
└─────────────────────────────────────────────────────────────┘
      │
      ▼
 real MCP server — completely unaware it's being paid for
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important design choice: payment gating happens at the raw HTTP layer, &lt;em&gt;before&lt;/em&gt; the request ever reaches MCP/JSON-RPC handling. That's what makes the 402 a genuine HTTP status code that x402-aware clients already know how to retry against, instead of a JSON-RPC error smuggled inside an HTTP 200.&lt;/p&gt;

&lt;p&gt;The owner keeps 85% of every paid call; the gateway keeps 15% for metering, verification, and payout bookkeeping. Pricing is per-tool, in USDC's 6-decimal base units, set in a single config entry — no changes to the upstream server's code at all.&lt;/p&gt;

&lt;p&gt;You can see it live and unpaid right now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://wazir-x402.duckdns.org/mcp-paywall/mcp/3ilm &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept: application/json, text/event-stream"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search_vulnerabilities","arguments":{"query":"oracle"}}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That returns a real &lt;code&gt;402&lt;/code&gt; with the price and &lt;code&gt;payTo&lt;/code&gt; address — no tool output, no data leak, exactly the behavior an x402-aware client needs to see to retry with a signed payment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I actually care about: verification is real, not a presence check
&lt;/h2&gt;

&lt;p&gt;The easy way to build something like this is to check "does an &lt;code&gt;X-PAYMENT&lt;/code&gt; header exist" and call it done. That's not verification, it's a formality.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;recoverEip3009Signer()&lt;/code&gt; does actual EIP-712 typed-data signature recovery against USDC's real domain (name "USD Coin", version "2", chain id 8453, the real Base USDC contract address) using &lt;code&gt;ethers.verifyTypedData&lt;/code&gt;. There's no "always valid" shortcut anywhere in that path. I proved this to myself by writing a test that tampers a single byte of a real, correctly-formed signature — it gets rejected because the recovered address genuinely no longer matches, not because of a format check catching malformed input.&lt;/p&gt;

&lt;p&gt;The full decision matrix that's tested end-to-end: missing payment, valid payment, replayed payment (same nonce twice), tampered signature, payment to the wrong address, underpayment, expired authorization, and a simulated facilitator failure on an otherwise-perfect payment. Every rejection path is checked twice — that it returns HTTP 402 (not 200), and that the response body never contains the real tool output. 50 automated tests, run against a real spawned instance of my own &lt;a href="https://www.npmjs.com/package/3ilm-mcp" rel="noopener noreferrer"&gt;3ilm-mcp&lt;/a&gt; server on npm (its actual 1,032-finding vulnerability dataset, not a stub).&lt;/p&gt;

&lt;p&gt;I also did one thing past the test suite: signed one real EIP-3009 authorization with a fresh, unfunded test wallet and sent it at the live public endpoint with the facilitator check turned on (not simulated). Every local check passed — structure, expiry, payee, amount, signature recovery — the request reached the real Coinbase facilitator over the network, and came back with an honest rejection (zero balance, as expected). No data leaked, nothing miscredited. That's the closest I got to a full production round-trip without actually moving money.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned building this
&lt;/h2&gt;

&lt;p&gt;Two things worth writing down, because neither is obvious until you hit it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A trusted-actor bug I found in my own code before anyone else could.&lt;/strong&gt; The first version of the owner dashboard had no auth at all — it was publicly readable. In a real multi-owner deployment, anyone who could guess or find a &lt;code&gt;serverId&lt;/code&gt; could read that owner's revenue numbers. I closed it with a per-server random token compared using a hashed constant-time check (SHA-256 both sides, then &lt;code&gt;crypto.timingSafeEqual&lt;/code&gt;, so neither a length mismatch nor timing tells you anything), verified with tests that assert the 401 body contains &lt;em&gt;zero&lt;/em&gt; revenue figures, not just a generic error. While I was in there I also found that the dashboard's read path re-scanned the entire ledger file on every hit, unbounded — a disk-I/O cost at volume, and separately an unthrottled oracle for guessing tokens. Fixed with a fixed-window rate limiter that runs &lt;em&gt;before&lt;/em&gt; the token check specifically, so brute-forcing the token is throttled too, not just legitimate traffic. I found both of these myself, in an adversarial pass against my own work, before showing it to anyone — that pass is the thing that actually caught them, not the first "it builds and the happy path works" version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A real crash that had nothing to do with my own logic.&lt;/strong&gt; Reusing one &lt;code&gt;StreamableHTTPServerTransport&lt;/code&gt; instance across sequential requests crashed the Node process outright on the second request — a native libuv assertion, Windows + Node 24 + the official MCP SDK. Root-caused to transport reuse, not anything in my gateway code, fixed by constructing a fresh Server+Transport pair per HTTP request. I haven't reproduced the original crash on Linux (the VPS where this actually runs), but the per-request-transport fix should hold regardless of platform, since it removes the shared state entirely rather than working around a platform quirk.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's genuinely not done yet
&lt;/h2&gt;

&lt;p&gt;Being specific here on purpose, because "it builds" has burned me before as a claim to make:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No real USDC has ever moved.&lt;/strong&gt; The facilitator call in the full round-trip above happened for real; the crypto is real; but no &lt;code&gt;transferWithAuthorization&lt;/code&gt; has ever been broadcast on-chain, and the payout script has never run against mainnet with a funded key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No real MCP client has driven this.&lt;/strong&gt; All testing went through raw HTTP calls speaking the same wire protocol a real agent would use — nothing has actually pointed Claude Desktop, Claude Code, or ChatGPT at this gateway and watched it pay and get an answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No load or concurrency testing.&lt;/strong&gt; Every paid call today is one child-process round-trip plus one facilitator round-trip; there's no queuing story yet for concurrent calls hitting the same upstream server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only one real server is integration-tested end-to-end&lt;/strong&gt; (my own 3ilm-mcp). It's the honest proof this works technically, not proof it works across the variety of MCP servers actually out there.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of that changes your read on "is this ready for my server" — it should. It's a working prototype with real cryptography and a live public endpoint, not a finished product.&lt;/p&gt;

&lt;h2&gt;
  
  
  About me / this project
&lt;/h2&gt;

&lt;p&gt;I'm a solo builder — no company, no team. I build these with AI-agent tooling as leverage (the code, the test suite, and the adversarial security pass on my own work were all done that way), but the design decisions, the verification of what's actually true versus claimed, and the "did this really work" checking are mine. If a number in this post turns out to be wrong, that's on me to fix, not the tooling.&lt;/p&gt;

&lt;p&gt;If you run an MCP server and the "nobody pays for this" problem sounds familiar, or if you can see a hole in the payment-verification logic I haven't found yet, I'd genuinely like to hear about it in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>mcp</category>
      <category>security</category>
    </item>
    <item>
      <title>The cheapest question you can ask before writing a PoC</title>
      <dc:creator>holistis</dc:creator>
      <pubDate>Mon, 03 Aug 2026 16:56:11 +0000</pubDate>
      <link>https://dev.to/holistis/the-cheapest-question-you-can-ask-before-writing-a-poc-2ae1</link>
      <guid>https://dev.to/holistis/the-cheapest-question-you-can-ask-before-writing-a-poc-2ae1</guid>
      <description>&lt;p&gt;If you hunt bug bounties, you know the pattern: you find something that looks real, you spend two or three days building a proof of concept, you submit it, and it comes back marked Duplicate. Someone else saw the same thing four hours before you did.&lt;/p&gt;

&lt;p&gt;This isn't a rare bad day. Write-ups from people who've done this for a while put duplicate and rejected reports at somewhere between 50 and 80 percent of everything submitted. That's not a skill problem. It's a timing and information problem: you can't know a bug is already known until after you've done the expensive part.&lt;/p&gt;

&lt;p&gt;Solodit already helps here — it's a free, searchable index of thousands of historical findings from Sherlock and Code4rena, and if you haven't used it, use it. But it lives in a browser tab you have to remember to check. It doesn't live inside the tools you're already using while you read the code.&lt;/p&gt;

&lt;p&gt;That's the specific gap we built &lt;a href="https://github.com/holistis/3ilm-mcp" rel="noopener noreferrer"&gt;3ilm-mcp&lt;/a&gt; for. It's a free MCP server — the same protocol Claude and other AI coding agents use to call tools — with a search index of 1,032 exact-reconciled findings from Sherlock audit contests, tagged by vulnerability pattern (reentrancy, oracle manipulation, access control, and so on), with acceptance rates per pattern so you can see which bug classes actually get paid versus which ones die at triage regardless of how correct they are.&lt;/p&gt;

&lt;p&gt;The point isn't that this replaces judgment. It's that the question "has this exact pattern been seen before, and does this category of finding usually survive triage" is worth asking before you sink two days into a PoC, not after a rejection tells you the answer for free.&lt;/p&gt;

&lt;p&gt;If you want the harder version of the same question answered — a full scan of a specific repo against known patterns, with an actual report — there's a paid tier for that too (&lt;a href="https://github.com/holistis/bug-bounty-intelligence-mcp" rel="noopener noreferrer"&gt;bug-bounty-intelligence-mcp&lt;/a&gt;, a few dollars via x402 or card). But the free pattern search costs nothing to try, and if it saves you from writing one more PoC for a bug someone already reported, it's already paid for itself.&lt;/p&gt;

&lt;p&gt;Install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; 3ilm-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then point your MCP-compatible agent at it and ask it whether a pattern you're looking at has come up before.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>I Built a Pay-Per-Call DeFi Bot Intelligence API Using x402 — No Signup Required</title>
      <dc:creator>holistis</dc:creator>
      <pubDate>Sat, 18 Jul 2026 21:15:14 +0000</pubDate>
      <link>https://dev.to/holistis/i-built-a-pay-per-call-defi-bot-intelligence-api-using-x402-no-signup-required-4odj</link>
      <guid>https://dev.to/holistis/i-built-a-pay-per-call-defi-bot-intelligence-api-using-x402-no-signup-required-4odj</guid>
      <description>&lt;p&gt;I run 50+ DeFi bots across Base, Arbitrum, OP and Solana. The data those bots generate — arb signals, draw schedules, earnings, health status — was sitting in PM2 logs on a Hetzner server. So I packaged it into an API and made it public.&lt;/p&gt;

&lt;p&gt;It's called the DeFi Signal API. Live on RapidAPI at &lt;a href="https://rapidapi.com/defisignalapi/api/defi-signal" rel="noopener noreferrer"&gt;rapidapi.com/defisignalapi/api/defi-signal&lt;/a&gt; and directly at &lt;code&gt;http://138.201.204.97:3748&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the API exposes
&lt;/h2&gt;

&lt;p&gt;Seven paid endpoints, all returning live data from real running bots:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET /api/pt-next&lt;/code&gt; - Next PoolTogether draw time on Base/Arb/OP/Scroll with countdown ($0.001)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /api/pt&lt;/code&gt; - PT draw scans and recent claim results from active claimer bots ($0.001)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /api/signals&lt;/code&gt; - Solana DEX arb near-win signals from the last 2 hours ($0.002)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /api/earnings&lt;/code&gt; - Bot earnings today across all chains, per bot ($0.003)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /api/health&lt;/code&gt; - Live PM2 status of all 50+ bots on Hetzner VPS ($0.002)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /api/murshid&lt;/code&gt; - Nightly strategy report: expected vs actual, lessons learned ($0.010)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /api/edge&lt;/code&gt; - Strategy verdicts with expected vs actual monthly returns ($0.008)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One free endpoint: &lt;code&gt;GET /api/status&lt;/code&gt; returns all prices and the payment wallet address.&lt;/p&gt;

&lt;h2&gt;
  
  
  How payment works: x402
&lt;/h2&gt;

&lt;p&gt;The x402 protocol turns payment into an HTTP header. No account. No API key. No signup form.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Call &lt;code&gt;GET /api/status&lt;/code&gt; to get the USDC wallet address on Base&lt;/li&gt;
&lt;li&gt;Send the exact USDC amount on Base mainnet&lt;/li&gt;
&lt;li&gt;Include the tx hash as &lt;code&gt;PAYMENT-SIGNATURE&lt;/code&gt; header&lt;/li&gt;
&lt;li&gt;Server verifies on-chain. Each tx hash works once.
&lt;/li&gt;
&lt;/ol&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;status&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://138.201.204.97:3748/api/status&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;wallet&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// After sending USDC to wallet on Base mainnet:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://138.201.204.97:3748/api/pt-next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;headers&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;PAYMENT-SIGNATURE&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;0x...your_tx_hash...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;draws&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// { base: { nextDrawAt: 1721390400000, timeUntilMs: 3600000 }, arb: {...} }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Transactions on Base settle in about 2 seconds. USDC transfer costs less than a cent. Pay-per-call is economically viable here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who this is for
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;PoolTogether bot builders who want precise draw timing without running their own RPC stack&lt;/li&gt;
&lt;li&gt;Solana arb researchers studying near-miss patterns to calibrate their own bots&lt;/li&gt;
&lt;li&gt;DeFi infrastructure teams wanting to monitor a live bot fleet without VPS access&lt;/li&gt;
&lt;li&gt;Strategy researchers who want real expected-vs-actual return data, not backtests&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What it does not do
&lt;/h2&gt;

&lt;p&gt;No transaction execution. No trading advice. No personal data. Pure data layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;OpenAPI spec: &lt;code&gt;http://138.201.204.97:3748/openapi.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/holistis/defi-signal-api" rel="noopener noreferrer"&gt;github.com/holistis/defi-signal-api&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;RapidAPI: &lt;a href="https://rapidapi.com/defisignalapi/api/defi-signal" rel="noopener noreferrer"&gt;rapidapi.com/defisignalapi/api/defi-signal&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Still building in public. If you need an endpoint that isn't there, open an issue or reach out.&lt;/p&gt;

</description>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>I Built a Self-Improving Health Platform: Five AI Agents That Learn Every Week</title>
      <dc:creator>holistis</dc:creator>
      <pubDate>Fri, 05 Jun 2026 22:25:05 +0000</pubDate>
      <link>https://dev.to/holistis/i-built-a-self-improving-health-platform-five-ai-agents-that-learn-every-week-1ci5</link>
      <guid>https://dev.to/holistis/i-built-a-self-improving-health-platform-five-ai-agents-that-learn-every-week-1ci5</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Originally published on &lt;a href="https://longevityai.nl/blog/multi-agent-self-improving-health-ai" rel="noopener noreferrer"&gt;longevityai.nl&lt;/a&gt;&lt;/strong&gt; — for full context, comments and related articles, visit the source.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  I Built a Self-Improving Health Platform: Five AI Agents That Learn Every Week
&lt;/h1&gt;

&lt;p&gt;Most AI products are static. You fine-tune a model, ship it, and it stays exactly as smart as the day you launched. Your users get the same quality on day 1 as on day 365.&lt;/p&gt;

&lt;p&gt;Mine doesn't work that way.&lt;/p&gt;

&lt;p&gt;Every Wednesday at 3am, five AI agents wake up, talk to each other, and make the next week's reports smarter — without me touching a single line of code.&lt;/p&gt;

&lt;p&gt;This is the architecture that makes it possible.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem with static AI products
&lt;/h2&gt;

&lt;p&gt;I run &lt;a href="https://longevityai.nl" rel="noopener noreferrer"&gt;Longevity AI&lt;/a&gt; — a health platform that generates personalized 6-month lifestyle plans from a 28-question intake. It cross-references 10+ organ systems, checks 400+ EFSA-regulated claims, and outputs a clinically-framed report in under 15 minutes.&lt;/p&gt;

&lt;p&gt;The core AI is Claude Sonnet. It's powerful. But it only knows what I've taught it.&lt;/p&gt;

&lt;p&gt;The problem: health science moves fast. A new PubMed paper on magnesium and sleep quality drops. My platform doesn't know. A patient with a rare medication combination comes in. The report might miss the interaction. A legal claim sneaks past review. Nobody catches it until a user complains.&lt;/p&gt;

&lt;p&gt;I had two options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hire a team of researchers and QA engineers to manually update the system&lt;/li&gt;
&lt;li&gt;Build agents that do it automatically&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I chose option 2. Here's exactly how it works.&lt;/p&gt;




&lt;h2&gt;
  
  
  The multi-agent architecture
&lt;/h2&gt;

&lt;p&gt;Five agents run on a fixed schedule. One orchestrates them all.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wednesday 03:00  Synthetic Patients Agent
Wednesday 04:00  Auto-KB Agent
Tuesday   03:30  Developer Tools Radar
Monday    07:00  Weekly Digest Agent
Always active    Agent Orchestrator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They don't share a runtime. They communicate through the database and a lightweight event system. No complex framework — just &lt;code&gt;reportAgentEvent()&lt;/code&gt; and a rules table.&lt;/p&gt;




&lt;h2&gt;
  
  
  Agent 1: Synthetic Patients
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The core of the self-improvement loop.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every Wednesday at 3am, 10 synthetic patient profiles are selected from a static template library (5 conditions x 2 psychological archetypes). These are fake patients with real-looking intake responses: ferritin levels, medication lists, trauma history, stress scores.&lt;/p&gt;

&lt;p&gt;Each synthetic patient goes through the exact same production pipeline as a real user. Full Sonnet report generation. No shortcuts.&lt;/p&gt;

&lt;p&gt;Then a second agent — Claude Haiku — scores each report on 4 dimensions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;What it checks&lt;/th&gt;
&lt;th&gt;Gap threshold&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Protocol depth&lt;/td&gt;
&lt;td&gt;Are expected correlations for this condition named?&lt;/td&gt;
&lt;td&gt;below 6/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Personalization&lt;/td&gt;
&lt;td&gt;Are this patient's specific details in the report?&lt;/td&gt;
&lt;td&gt;below 6/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Supplement specificity&lt;/td&gt;
&lt;td&gt;Active biological forms named (magnesium bisglycinate, not just magnesium)?&lt;/td&gt;
&lt;td&gt;below 6/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Legal safety&lt;/td&gt;
&lt;td&gt;No forbidden medical claims, no stop-medication advice?&lt;/td&gt;
&lt;td&gt;below 7/10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Scores below threshold become knowledge gap proposals. Legal safety below 5 triggers an immediate compliance scan — synchronously, before anything else continues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost: ~€0.55/week.&lt;/strong&gt; Sonnet for the reports, Haiku for scoring.&lt;/p&gt;




&lt;h2&gt;
  
  
  Agent 2: Auto-KB
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The knowledge base that writes itself.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The gap proposals from Agent 1 contain condition types and dimensions. Agent 2 converts these into PubMed queries, fetches abstracts via the free NCBI API, and sends each abstract to Haiku with one instruction:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Extract 3-5 factual claims from this abstract that are directly relevant to [condition]. Return structured triples: subject, predicate, object.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The triples land in a &lt;code&gt;knowledge_triples&lt;/code&gt; table. The report generator reads from this table at runtime. No retraining. No fine-tuning. Just better context for the next generation.&lt;/p&gt;

&lt;p&gt;By Wednesday afternoon, the knowledge base has been updated with whatever the synthetic patients couldn't answer well. By Thursday morning, real patients get smarter reports.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The feedback loop:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Synthetic patient gets weak report
  → Gap detected
    → PubMed abstract fetched
      → Facts extracted
        → knowledge_triples updated
          → Next patient gets stronger report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Agent 3: Developer Tools Radar
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Because staying current is also a product decision.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every Tuesday at 3:30am, the radar scans GitHub Trending and dev.to for tools that match a static relevance filter: playwright, trpc, drizzle, anthropic, health, automation, react, typescript.&lt;/p&gt;

&lt;p&gt;Haiku summarizes each match in 1-2 sentences. The summaries land in the admin UI. Monday morning I get a digest with what the dev world built this week that's relevant to my stack.&lt;/p&gt;

&lt;p&gt;Cost: €0.04/month.&lt;/p&gt;




&lt;h2&gt;
  
  
  Agent 4: The Orchestrator
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The rule engine that connects everything.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each agent calls &lt;code&gt;reportAgentEvent(type, result)&lt;/code&gt; when it finishes. The orchestrator applies rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// R1: Legal flag → immediate compliance scan&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;synthetic_loop_done&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;legalFlags&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;runComplianceDriftScan&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// synchronous, not queued&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// R2: KB pipeline returned 0 facts → warning&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auto_kb_done&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;factsInserted&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[orchestrator] Auto-KB returned 0 facts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rule R1 is the critical one. If a synthetic patient triggers a legal safety score below 5, the compliance agent doesn't wait until next week. It runs immediately. The orchestrator also lets me add new rules without touching the agents themselves.&lt;/p&gt;




&lt;h2&gt;
  
  
  Agent 5: Weekly Digest
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The operator dashboard I never have to build.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every Monday at 7am, an HTML email lands in my inbox with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How many new facts entered the knowledge base this week&lt;/li&gt;
&lt;li&gt;Synthetic loop results: gaps found, legal flags if any&lt;/li&gt;
&lt;li&gt;Which PubMed papers were automatically processed&lt;/li&gt;
&lt;li&gt;Which developer tools are on the radar&lt;/li&gt;
&lt;li&gt;System cost for the week&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I know exactly what the system learned, what it fixed, and what it flagged without logging into a dashboard or running queries.&lt;/p&gt;




&lt;h2&gt;
  
  
  Psychological profiling at €0 extra
&lt;/h2&gt;

&lt;p&gt;While building the agent system, I added something that costs literally nothing.&lt;/p&gt;

&lt;p&gt;Every intake has responses about stress levels, anxiety, medication history, trauma, and previous treatment attempts. From these existing fields — no new questions, no LLM call — I derive a psychological archetype:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Archetype&lt;/th&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Coaching instruction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Overwhelmed&lt;/td&gt;
&lt;td&gt;High stress + multiple specialists + frustration keywords&lt;/td&gt;
&lt;td&gt;Start with deep validation, cite their own words, tiny steps only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skeptical&lt;/td&gt;
&lt;td&gt;Multiple specialists + frustration, low anxiety&lt;/td&gt;
&lt;td&gt;Biological mechanism first, then recommendation. Name the researcher.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ready but scared&lt;/td&gt;
&lt;td&gt;Fear keywords + previous attempts + moderate stress&lt;/td&gt;
&lt;td&gt;Week 1 max 2 changes, explicit success markers per week&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Knowledge-seeker&lt;/td&gt;
&lt;td&gt;Blood values mentioned + detailed answers + no fear&lt;/td&gt;
&lt;td&gt;Enzymes and neurotransmitters before the advice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Beginner&lt;/td&gt;
&lt;td&gt;No prior treatment, short answers&lt;/td&gt;
&lt;td&gt;Warm, jargon-free, reassuring timeline&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The archetype gets injected as a single instruction line into the report system prompt. The LLM adapts tone, structure, and depth automatically. Zero extra tokens at generation time.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this cost to build
&lt;/h2&gt;

&lt;p&gt;The entire multi-agent system is about 1,200 lines of TypeScript. It took one focused session to architect and implement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Weekly operating cost:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Synthetic loop: max €0.55&lt;/li&gt;
&lt;li&gt;Auto-KB pipeline: ~€0.01&lt;/li&gt;
&lt;li&gt;Developer radar: ~€0.01&lt;/li&gt;
&lt;li&gt;Weekly digest: €0 (DB queries + one email)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Total: under €0.60/week&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The system pays for itself if it catches one legal compliance issue before a user does.&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;Haiku scores need calibration.&lt;/strong&gt; The first few weeks I'll manually compare Haiku's scores against my own judgment and adjust thresholds if needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Synthetic patient templates are static.&lt;/strong&gt; They don't learn. The knowledge base learns, but the patient profiles stay fixed. A future version would generate edge-case profiles dynamically based on what real users submit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The orchestrator is in-memory.&lt;/strong&gt; Events don't survive a server restart. For a high-stakes system I'd persist the event log. For a solo SaaS at this scale, it's fine.&lt;/p&gt;




&lt;h2&gt;
  
  
  The bigger point
&lt;/h2&gt;

&lt;p&gt;Most solo founders think "multi-agent AI" means using CrewAI or AutoGen with 10 chained LLM calls. That's one way to do it.&lt;/p&gt;

&lt;p&gt;What I have is simpler and more reliable: purpose-built agents that do one thing well, communicate through a database, and are coordinated by a lightweight rule engine. No framework. No magic. Just TypeScript, cron jobs, and a clear separation of concerns.&lt;/p&gt;

&lt;p&gt;The result: a platform that gets measurably smarter every week, catches its own legal issues, updates its own knowledge base, and tells me what it learned while I sleep.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built on: React 18 + tRPC + Drizzle/MySQL + Claude Sonnet/Haiku + Railway&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Live at &lt;a href="https://longevityai.nl" rel="noopener noreferrer"&gt;longevityai.nl&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Questions or want to talk architecture? &lt;a href="mailto:info@holistischadviseur.nl"&gt;info@holistischadviseur.nl&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href="https://longevityai.nl/blog/multi-agent-self-improving-health-ai" rel="noopener noreferrer"&gt;Longevity AI&lt;/a&gt;. Visit the source for the full context, references and discussion.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>technical</category>
      <category>ai</category>
      <category>multiagent</category>
      <category>automation</category>
    </item>
    <item>
      <title>I Added Legal Compliance Checks to My E2E Test Suite for €0. Here's How (and Why a Non-Developer Had To).</title>
      <dc:creator>holistis</dc:creator>
      <pubDate>Fri, 05 Jun 2026 10:23:25 +0000</pubDate>
      <link>https://dev.to/holistis/i-added-legal-compliance-checks-to-my-e2e-test-suite-for-eu0-heres-how-and-why-a-non-developer-45h2</link>
      <guid>https://dev.to/holistis/i-added-legal-compliance-checks-to-my-e2e-test-suite-for-eu0-heres-how-and-why-a-non-developer-45h2</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Originally published on &lt;a href="https://longevityai.nl/blog/muraqib-130-tests-compliance-as-code" rel="noopener noreferrer"&gt;longevityai.nl&lt;/a&gt;&lt;/strong&gt; — for full context, comments and related articles, visit the source.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  I Added Legal Compliance Checks to My E2E Test Suite for €0. Here's How (and Why a Non-Developer Had To).
&lt;/h1&gt;

&lt;p&gt;I am not a developer. I run a health AI platform in the Netherlands called &lt;a href="https://longevityai.nl" rel="noopener noreferrer"&gt;Longevity AI&lt;/a&gt;. It generates personalized health reports using LLMs, auto-publishes blog posts about longevity, and operates under Dutch medical regulation: the IGJ (Health and Youth Care Inspectorate), Wet BIG, and GDPR.&lt;/p&gt;

&lt;p&gt;That last part is the part most builders skip. I cannot.&lt;/p&gt;

&lt;p&gt;If my platform publishes content that says "this supplement cures your condition" or "stop taking your medication," I am not looking at bad SEO. I am looking at an enforcement action by the IGJ. The Dutch health regulator has shut down platforms for exactly this kind of language. I have a file in my codebase called &lt;code&gt;risks.md&lt;/code&gt; specifically about this.&lt;/p&gt;

&lt;p&gt;Today I shipped something I think is useful for anyone building in a regulated space: a compliance scanner baked directly into my Playwright E2E test suite. It runs on every deploy. It costs €0. It uses no LLM.&lt;/p&gt;

&lt;p&gt;Here is what it does and why I built it this way.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5hsgxflsvqbjacpsyj7g.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5hsgxflsvqbjacpsyj7g.jpeg" alt="Illustratie bij I Added Legal Compliance Checks to My E2E Test Suite for €0. Here's How (and Why a Non-Developer Had To)." width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: AI-generated content drifts toward prohibited language
&lt;/h2&gt;

&lt;p&gt;My platform runs a nightly pipeline called Autopilot News Radar. It pulls from BBC Health, PubMed, EFSA, and ClinicalTrials, then generates three-language blog posts (NL/EN/FR) automatically. The posts go through a server-side compliance scanner before they are saved.&lt;/p&gt;

&lt;p&gt;That scanner works. It has caught violations before they went live.&lt;/p&gt;

&lt;p&gt;But I had a gap: no automated check was running against the &lt;em&gt;actual live site&lt;/em&gt;. The server filter could pass a post, and some edge case in rendering, caching, or a manual publish could still get prohibited language onto the page. I would only discover that when a user flagged it -- or worse, a regulator did.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution: compliance.spec.ts
&lt;/h2&gt;

&lt;p&gt;I added four Playwright tests that fetch real pages from the live site and run the same regex patterns the server uses.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tools/muraqib/tests/compliance.spec.ts (simplified)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HIGH_SEVERITY_PATTERNS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ruleId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no-cure-claim&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;geneest&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;gegarandeerd|altijd|100%|volledig&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/i&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ruleId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no-cure-claim&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;wondermiddel&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;/i&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ruleId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no-cure-claim&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;100%&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+effectief&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;/i&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ruleId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no-behandel-imperatief&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/stop&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+met&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;je|jouw|de&lt;/span&gt;&lt;span class="se"&gt;)\s&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;medicatie|medicijn&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/i&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ruleId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no-behandel-imperatief&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;dit&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+vervangt&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;medicatie|behandeling&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/i&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;geen HIGH compliance-schendingen in live blogs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/blog&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;waitForReady&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&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;links&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a[href*="/blog/"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;evaluateAll&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;els&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;els&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLAnchorElement&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;href&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="na"&gt;violations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="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;url&lt;/span&gt; &lt;span class="k"&gt;of&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;links&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&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;text&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;article&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="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;ruleId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pattern&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;HIGH_SEVERITY_PATTERNS&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;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pattern&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;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;violations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ruleId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s2"&gt;" op &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&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="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;violations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HIGH compliance-schending(en) gevonden op live site&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No API calls. No LLM. A regex scan on the live rendered HTML, run by a real browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why regex and not an LLM?
&lt;/h2&gt;

&lt;p&gt;Because I need determinism and zero cost.&lt;/p&gt;

&lt;p&gt;An LLM-based compliance check would:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cost money on every deploy (I run 130 tests per deploy, nightly)&lt;/li&gt;
&lt;li&gt;Be non-deterministic (same text, different answer on reruns)&lt;/li&gt;
&lt;li&gt;Be a black box ("the AI said it was fine" is not an audit trail)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The patterns I check are not subtle. The IGJ is not looking for nuanced phrasing. They are looking for things like "genezingsclaims" (cure claims) and advice to stop medication. Regex handles this precisely and cheaply.&lt;/p&gt;

&lt;h2&gt;
  
  
  The double-layer architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[AI generates content]
        |
        v
[server/compliance/scanner.ts]      blocks HIGH violations before save
        |
        v
[database -- rendered on site]
        |
        v
[tools/muraqib/tests/compliance.spec.ts]   E2E test on live page, every deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the first layer fails (bug in server, manual override, edge case), the second layer catches it before the deploy completes. If the second layer catches something, GitHub Actions fails and I get notified.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyzuc9vzcnxa7x4vjsmxg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyzuc9vzcnxa7x4vjsmxg.jpeg" alt="Illustratie bij I Added Legal Compliance Checks to My E2E Test Suite for €0. Here's How (and Why a Non-Developer Had To)." width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The four compliance tests
&lt;/h2&gt;

&lt;p&gt;Beyond the HIGH-severity check, the suite also runs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Blog overview loads&lt;/strong&gt; -- sanity check that /blog renders with at least one article&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No HIGH violations&lt;/strong&gt; -- cure claims and medication-stop advice on live pages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No em-dashes in public text&lt;/strong&gt; -- house style rule enforced automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Affiliate disclosure present&lt;/strong&gt; -- blogs with affiliate links must contain a disclosure sentence&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last one is a legal requirement in the Netherlands too. If I link to a supplement with an affiliate ref, I need to disclose it. The test checks that the disclosure text is present on any page that contains an affiliate link.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does NOT catch
&lt;/h2&gt;

&lt;p&gt;I want to be honest about the limits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MEDIUM-severity violations are manual.&lt;/strong&gt; Vague implied efficacy claims are contextual. My server scanner flags them and logs them. A human (me) reviews the log weekly. Automated blocking on MEDIUM creates too many false positives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It does not understand context.&lt;/strong&gt; "Stop je medicatie" in a quote about why you should &lt;em&gt;not&lt;/em&gt; stop medication would still trigger the pattern. I have handled this by writing content that avoids the phrase entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It is not a legal opinion.&lt;/strong&gt; These patterns are based on documented compliance research. They are not a substitute for a lawyer reviewing the content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Also shipped today: AI Coach tests
&lt;/h2&gt;

&lt;p&gt;Alongside the compliance spec, I also added &lt;code&gt;coach.spec.ts&lt;/code&gt; -- eight tests for the AI Coach feature:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Login gate: unauthenticated users see a CTA, not the chat interface&lt;/li&gt;
&lt;li&gt;Sending a message and receiving a response within timeout&lt;/li&gt;
&lt;li&gt;Enter-key submission (this broke silently once before)&lt;/li&gt;
&lt;li&gt;Disabled send button on empty input (prevents empty API calls)&lt;/li&gt;
&lt;li&gt;Feedback buttons (thumbs up/down) working correctly&lt;/li&gt;
&lt;li&gt;Back navigation to reports page&lt;/li&gt;
&lt;li&gt;Zero JavaScript console errors during a full conversation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is the test I trust most. A zero-console-error check on a full user flow catches a class of bugs no unit test finds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Muraqib stands today
&lt;/h2&gt;

&lt;p&gt;130 tests across: SEO, sitemap, robots.txt, blog rendering, anamnesis flow, lab analysis, onboarding, AI Coach, and compliance. The suite runs on GitHub Actions using Playwright 1.60.0 with the native Healer active -- it auto-repairs locators that break when the DOM changes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Before today&lt;/th&gt;
&lt;th&gt;After today&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Total tests&lt;/td&gt;
&lt;td&gt;118&lt;/td&gt;
&lt;td&gt;130&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Playwright version&lt;/td&gt;
&lt;td&gt;1.48&lt;/td&gt;
&lt;td&gt;1.60.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compliance coverage&lt;/td&gt;
&lt;td&gt;Server only&lt;/td&gt;
&lt;td&gt;Server + E2E&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI Coach coverage&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;8 tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Healer&lt;/td&gt;
&lt;td&gt;Not active&lt;/td&gt;
&lt;td&gt;Active in CI&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every deploy runs all 130. A green check means a real browser walked through the platform and found nothing wrong. A red check means something broke before any user sees it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why ownership matters here
&lt;/h2&gt;

&lt;p&gt;There are SaaS compliance monitoring tools. All of them put my legal patterns in a vendor's system, behind a subscription.&lt;/p&gt;

&lt;p&gt;My patterns live in &lt;code&gt;tools/muraqib/tests/compliance.spec.ts&lt;/code&gt;. They are in git. They have commit history. When the IGJ updates their enforcement guidance, I add a line and commit it. The next deploy runs the updated check.&lt;/p&gt;

&lt;p&gt;That is the practical argument for owning your test infrastructure: the rules that matter most to your business should live where you can see them, change them, and audit them.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Longevity AI runs at &lt;a href="https://longevityai.nl" rel="noopener noreferrer"&gt;longevityai.nl&lt;/a&gt;. If you are building a health SaaS in a regulated industry and want to compare notes, reach out via the site.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href="https://longevityai.nl/blog/muraqib-130-tests-compliance-as-code" rel="noopener noreferrer"&gt;Longevity AI&lt;/a&gt;. Visit the source for the full context, references and discussion.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>technical</category>
      <category>testing</category>
      <category>buildinpublic</category>
      <category>ai</category>
    </item>
    <item>
      <title>I Didn't Buy Octomind. I Built My Own for €0. Here's Why It's Better.</title>
      <dc:creator>holistis</dc:creator>
      <pubDate>Tue, 02 Jun 2026 20:36:48 +0000</pubDate>
      <link>https://dev.to/holistis/i-didnt-buy-octomind-i-built-my-own-for-eu0-heres-why-its-better-6lj</link>
      <guid>https://dev.to/holistis/i-didnt-buy-octomind-i-built-my-own-for-eu0-heres-why-its-better-6lj</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Originally published on &lt;a href="https://longevityai.nl/blog/i-built-my-own-octomind-for-zero-euros-muraqib" rel="noopener noreferrer"&gt;longevityai.nl&lt;/a&gt;&lt;/strong&gt; — for full context, comments and related articles, visit the source.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  I Didn't Buy Octomind. I Built My Own for €0. Here's Why It's Better.
&lt;/h1&gt;

&lt;p&gt;I am not a developer. I run &lt;a href="https://longevityai.nl" rel="noopener noreferrer"&gt;Longevity AI&lt;/a&gt;, a Dutch health AI platform. I have paying users, a compliance gate, three languages, and a codebase that keeps growing every week.&lt;/p&gt;

&lt;p&gt;And last month I discovered Octomind — an AI-powered e2e testing platform that automatically generates and self-heals your Playwright tests. The pitch was compelling. The price tag was not: €200-500+ per month.&lt;/p&gt;

&lt;p&gt;So I built my own.&lt;/p&gt;

&lt;p&gt;I called it &lt;strong&gt;Muraqib&lt;/strong&gt; (مراقب) — Arabic for "the observer." It runs every night. It fixes itself. And it costs €0.&lt;/p&gt;

&lt;p&gt;Here is why that was the right call.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Octomind Does (And Does Well)
&lt;/h2&gt;

&lt;p&gt;To be fair: Octomind is genuinely impressive. You give it a URL, it crawls your site, generates Playwright tests automatically, and when your UI changes, it heals the broken selectors without you touching a config file. The dashboard shows you video recordings of every test run. It integrates with GitHub in ten minutes.&lt;/p&gt;

&lt;p&gt;For a team, it is probably worth the money. The time saved on test maintenance alone justifies the cost at scale.&lt;/p&gt;

&lt;p&gt;But I am not a team. I am one person running a SaaS in a regulated industry. And when I looked at what Octomind would actually give me versus what I actually needed, the math did not work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem With Generic QA Tools
&lt;/h2&gt;

&lt;p&gt;Here is what a generic tool does not know about my site:&lt;/p&gt;

&lt;p&gt;My blog uses &lt;strong&gt;Wouter&lt;/strong&gt; for client-side routing. Every blog article is an &lt;code&gt;&amp;lt;article&amp;gt;&lt;/code&gt; element with an &lt;code&gt;onClick&lt;/code&gt; handler — not an &lt;code&gt;&amp;lt;a href&amp;gt;&lt;/code&gt; link. A generic crawler would generate selectors like &lt;code&gt;a[href*="/blog/"]&lt;/code&gt; and they would fail on day one. Not because the feature is broken. Because the tool does not understand how the page actually works.&lt;/p&gt;

&lt;p&gt;My welzijnscheck funnel has two different pages: a landing page at &lt;code&gt;/welzijnscheck&lt;/code&gt; and the actual questionnaire at &lt;code&gt;/welzijnscheck/:code&lt;/code&gt;. A test that looks for a form on the landing page will fail every time. Not a bug. Just architecture the tool has no context for.&lt;/p&gt;

&lt;p&gt;These are not edge cases. They are the normal state of a custom-built SaaS. Generic tools generate generic tests. Generic tests fail in non-generic codebases.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7s2wft3ei0u4c5vy3kj7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7s2wft3ei0u4c5vy3kj7.png" alt="Illustratie bij I Didn't Buy Octomind. I Built My Own for €0. Here's Why It's Better." width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Muraqib Does Differently
&lt;/h2&gt;

&lt;p&gt;Muraqib knows my codebase because it was built from it.&lt;/p&gt;

&lt;p&gt;Every selector in every test spec reflects how my application actually works — not how a crawler assumes it works. When a test is written for the welzijnscheck flow, it knows that &lt;code&gt;/welzijnscheck&lt;/code&gt; is a landing page with CTA buttons, and that the form lives at a different route entirely. That knowledge does not come from crawling. It comes from reading the source.&lt;/p&gt;

&lt;p&gt;The self-healing works the same way. When a test fails, Claude Code reads the failure, reads the relevant spec, and reads the production code. The fix it proposes is based on understanding the full context — not a pattern-matched selector swap.&lt;/p&gt;

&lt;p&gt;That is the difference between a tool that knows your URL and a tool that knows your codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Numbers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Octomind&lt;/th&gt;
&lt;th&gt;Muraqib&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Monthly cost&lt;/td&gt;
&lt;td&gt;€200-500+&lt;/td&gt;
&lt;td&gt;€0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test generation&lt;/td&gt;
&lt;td&gt;Automatic (crawler)&lt;/td&gt;
&lt;td&gt;Built from source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-healing&lt;/td&gt;
&lt;td&gt;Automatic selector repair&lt;/td&gt;
&lt;td&gt;Claude Code reads source + fixes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dashboard&lt;/td&gt;
&lt;td&gt;Beautiful UI, video recordings&lt;/td&gt;
&lt;td&gt;GitHub Actions + weekly email&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Notifications&lt;/td&gt;
&lt;td&gt;Configurable&lt;/td&gt;
&lt;td&gt;One email per week, Monday 08:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vendor lock-in&lt;/td&gt;
&lt;td&gt;Yes (their platform)&lt;/td&gt;
&lt;td&gt;No (your code, your repo)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Knows your routing&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Knows your architecture&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The €0 is not the main point. The &lt;strong&gt;codebase awareness&lt;/strong&gt; is.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Weekly Email
&lt;/h2&gt;

&lt;p&gt;One design decision I made that I have not seen elsewhere: Muraqib sends me one email per week. Not one per failure. Not one per test run. One.&lt;/p&gt;

&lt;p&gt;Every Monday at 08:00, I get a summary: how many runs this week, pass rate, what failed, what Claude fixed, which PRs are open for my review.&lt;/p&gt;

&lt;p&gt;That is it. Everything else happens without me.&lt;/p&gt;

&lt;p&gt;Most monitoring tools optimize for visibility. More dashboards, more notifications, more insight. I optimized for silence. I want to know about problems only after Muraqib has already tried to fix them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fazgtlmgfm3yt9aoszf1z.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fazgtlmgfm3yt9aoszf1z.jpeg" alt="Illustratie bij I Didn't Buy Octomind. I Built My Own for €0. Here's Why It's Better." width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Self-Healing Loop
&lt;/h2&gt;

&lt;p&gt;When a test fails at 03:00 AM:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;GitHub Actions creates an Issue with the full failure context&lt;/li&gt;
&lt;li&gt;The Issue tags &lt;code&gt;@claude&lt;/code&gt; — the Claude Code GitHub App picks it up&lt;/li&gt;
&lt;li&gt;Claude reads the failing test, the error message, and the relevant production code&lt;/li&gt;
&lt;li&gt;Claude opens a Pull Request with a proposed fix&lt;/li&gt;
&lt;li&gt;I review it Monday morning, merge in 30 seconds&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is not magic. It is just a well-connected pipeline. But the result is that my test suite heals itself on a cadence that matches my review capacity — not the cadence of failure alerts.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Gave Up
&lt;/h2&gt;

&lt;p&gt;I want to be honest about the trade-offs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I gave up the beautiful UI.&lt;/strong&gt; Octomind has a proper dashboard with pass/fail history, visual diffs, and video of every test run. My "dashboard" is a GitHub Actions log and a Monday morning email. For a solo founder checking status once a week, that is fine. For a team doing daily deployments, it would not be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I gave up automatic test generation.&lt;/strong&gt; Octomind crawls your site and writes the tests. I wrote mine manually (with Claude). That was a few hours of work upfront. The trade-off is that every test reflects exactly what I intend to test — no assumptions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I gave up instant failure notifications.&lt;/strong&gt; Octomind can alert you in minutes. I check once a week. This works because Claude tries to fix things before they need my attention. If your business model requires instant incident response, this approach is not for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Ownership Matters
&lt;/h2&gt;

&lt;p&gt;There is a version of this story where I sign up for Octomind, pay the monthly fee, and move on. That is a legitimate choice.&lt;/p&gt;

&lt;p&gt;But here is what I would have missed:&lt;/p&gt;

&lt;p&gt;When I updated my blog routing from anchor tags to wouter's &lt;code&gt;setLocation()&lt;/code&gt; handler, my tests would have broken. With Octomind, I would file a support ticket or wait for the self-healing to catch up. With Muraqib, Claude already knew about the routing change because it made the change. The fix was in the same PR.&lt;/p&gt;

&lt;p&gt;That is the compounding advantage of ownership. The tool that built your feature is the same tool that tests it and fixes it when it breaks. There is no translation layer between what the code does and what the tests expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Name
&lt;/h2&gt;

&lt;p&gt;Muraqib (مراقب) is Arabic for "the observer" or "the supervisor." Not a divine name — just a word with weight. It fits what the tool does: watch quietly, act only when something is wrong.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"He sees what you do not see. He notices what you forgot. He is not a guard that shouts. He is a guard that fixes."&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Longevity AI runs at &lt;a href="https://longevityai.nl" rel="noopener noreferrer"&gt;longevityai.nl&lt;/a&gt;. If you are building a health SaaS in a regulated industry and want to compare notes, I am reachable via the site.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href="https://longevityai.nl/blog/i-built-my-own-octomind-for-zero-euros-muraqib" rel="noopener noreferrer"&gt;Longevity AI&lt;/a&gt;. Visit the source for the full context, references and discussion.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>technical</category>
      <category>testing</category>
      <category>buildinpublic</category>
      <category>ai</category>
    </item>
    <item>
      <title>Why I Built a Halal-By-Design AI Image Pipeline</title>
      <dc:creator>holistis</dc:creator>
      <pubDate>Tue, 02 Jun 2026 16:52:05 +0000</pubDate>
      <link>https://dev.to/holistis/why-i-built-a-halal-by-design-ai-image-pipeline-1bec</link>
      <guid>https://dev.to/holistis/why-i-built-a-halal-by-design-ai-image-pipeline-1bec</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Originally published on &lt;a href="https://longevityai.nl/blog/halal-by-design-ai-image-pipeline" rel="noopener noreferrer"&gt;longevityai.nl&lt;/a&gt;&lt;/strong&gt; — for full context, comments and related articles, visit the source.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Why I Built a Halal-By-Design AI Image Pipeline
&lt;/h1&gt;

&lt;p&gt;I run &lt;a href="https://longevityai.nl" rel="noopener noreferrer"&gt;Longevity AI&lt;/a&gt;, a Dutch holistic health platform. When I plugged Stable Diffusion into my blog pipeline, the first batch of "wellness" images came back with a Buddha statue, a Catholic crucifix on a yoga studio wall, and a person doing salah pose toward Mecca. My audience is mixed. Muslim, Christian, Hindu, secular. I needed a filter.&lt;/p&gt;

&lt;p&gt;So I built one. I called it the halal filter, because halal is the strictest standard. If an image passes halal, it passes for everyone. Catholic readers, secular yoga teachers, kids using the platform, atheist HR buyers. The strictest filter is also the most universal one.&lt;/p&gt;

&lt;p&gt;Here is what I built, why "halal" is a useful label even outside Islam, and what you can copy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: AI Image Generators Have No Brand Sense
&lt;/h2&gt;

&lt;p&gt;Stable Diffusion, Flux, DALL-E. They generate religious imagery by default if your prompt has any spiritual undertone. Ask for "meditation" and you get a Buddha. Ask for "calm bedroom" and you get a candle next to a crucifix. Ask for "morning routine" and you get prayer beads.&lt;/p&gt;

&lt;p&gt;The AI is not wrong. It reflects training data biased toward stock photography where meditation equals Buddha and serenity equals religious iconography. That is a fact about the internet, not about meditation.&lt;/p&gt;

&lt;p&gt;For a Dutch holistic health platform this is a brand nightmare:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Muslim readers see a Buddha in a wellness article and close the tab.&lt;/li&gt;
&lt;li&gt;Christian readers see a Hindu deity and wonder what kind of platform this is.&lt;/li&gt;
&lt;li&gt;Secular readers see religious symbols and think the site is preachy.&lt;/li&gt;
&lt;li&gt;Schools using the B2B bundle see a deity statue and parents complain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The image is fine for an American wellness site where Buddha aesthetics are baseline. It is not fine for the Netherlands, where the audience is religiously plural and the trust threshold is high.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkx7suxdaar2f6ym72xs8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkx7suxdaar2f6ym72xs8.png" alt="Illustratie bij Why I Built a Halal-By-Design AI Image Pipeline" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Halal Lens (Why It Matters Even If You Are Not Muslim)
&lt;/h2&gt;

&lt;p&gt;Halal as a concept goes further than most filters. It says: do not depict the divine, do not encourage worship of created things, do not show prayer rituals as decoration, do not undress for the camera. The Islamic prohibition on imagery of deities, combined with strict modesty rules, creates a very high bar.&lt;/p&gt;

&lt;p&gt;Here is the key insight. If your image pipeline passes halal, it passes every other religious and secular standard too.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Catholic parent will not be offended (no other gods).&lt;/li&gt;
&lt;li&gt;A Buddhist will not see their iconography mishandled.&lt;/li&gt;
&lt;li&gt;A secular user will not see religious symbols at all.&lt;/li&gt;
&lt;li&gt;A modesty-conscious brand will not see exposed bodies.&lt;/li&gt;
&lt;li&gt;A child-safety reviewer will not see anything they need to flag.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reverse is not true. A Buddhist-safe image might still contain a crucifix. A secular-clean image might still show prayer beads. Halal is the upper bound of restrictiveness, which is exactly why it makes a great brand-safety default for any audience.&lt;/p&gt;

&lt;p&gt;I do not think non-Muslim devs need to become Muslim to use this filter. I think they should steal the idea. Strictest filter equals most universal compatibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Code (Less Than 100 Lines)
&lt;/h2&gt;

&lt;p&gt;Here is the actual blacklist (50+ keywords, no LLM call needed):&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;RELIGIOUS_BLACKLIST&lt;/span&gt; &lt;span class="o"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;buddha&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="s2"&gt;buddhism&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="s2"&gt;deity&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="s2"&gt;gods&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="s2"&gt;god&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="s2"&gt;worship&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="s2"&gt;prayer&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="s2"&gt;praying&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="s2"&gt;temple&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="s2"&gt;shrine&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="s2"&gt;sacred&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="s2"&gt;holy&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="s2"&gt;altar&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="s2"&gt;religious&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="s2"&gt;monk&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="s2"&gt;nun&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="s2"&gt;bible&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="s2"&gt;quran&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="s2"&gt;torah&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="s2"&gt;church&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="s2"&gt;mosque&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="s2"&gt;synagogue&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="s2"&gt;hinduism&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="s2"&gt;hindu&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="s2"&gt;brahmin&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="s2"&gt;idol&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="s2"&gt;idolatry&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="s2"&gt;cross&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="s2"&gt;crucifix&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="s2"&gt;saint&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="s2"&gt;miracle&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="s2"&gt;spiritual&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="s2"&gt;mystical&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="s2"&gt;ceremonial&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="s2"&gt;ritual&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="s2"&gt;chakra&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="s2"&gt;mantra&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="s2"&gt;zen&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="s2"&gt;tibetan&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="s2"&gt;vatican&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="s2"&gt;pilgrim&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="s2"&gt;communion&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="s2"&gt;baptism&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sanitizeImagePrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&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;lower&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&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;banned&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;RELIGIOUS_BLACKLIST&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;banned&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;prompt&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;banned&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gi&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="s2"&gt;wellness&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&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;prompt&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 is the whole thing. Two pieces: a blacklist set, and a sanitizer that replaces any flagged term with the neutral word "wellness".&lt;/p&gt;

&lt;p&gt;The sanitizer is called before every image-generation API call (Stability AI, Flux, DALL-E, even Pexels search). The image API never gets a chance to generate the offending image, because the prompt is rewritten before it leaves my server.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Universal Lesson: Hardcode Your Brand Boundaries
&lt;/h2&gt;

&lt;p&gt;You cannot trust the LLM to know your brand. Stable Diffusion does not know my audience is Dutch-Muslim. DALL-E does not know my B2B clients include schools. Flux does not know my CEO is religious.&lt;/p&gt;

&lt;p&gt;Brand boundaries belong in code, not in prompts.&lt;/p&gt;

&lt;p&gt;The mistake most teams make is telling the LLM "do not include religious imagery" in the system prompt. This works 80% of the time. The 20% leak is what gets you on Twitter. A hardcoded blacklist plus a sanitizer is 100%. Every image-prompt passes through. No exceptions. No "the AI was being creative".&lt;/p&gt;

&lt;p&gt;Even if you do not care about religious imagery specifically, you almost certainly care about something else:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Children's apps:&lt;/strong&gt; no firearms, no violence, no romantic imagery.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Medical platforms:&lt;/strong&gt; no procedures shown out of context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;B2B SaaS:&lt;/strong&gt; no political symbols, no celebrities, no competitor logos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;News publishers:&lt;/strong&gt; no copyrighted IP.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern is the same. Build a blacklist. Build a sanitizer. Never trust the LLM to enforce your brand. It does not know your brand.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3sgk5wvdgsytjwxcgxfv.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3sgk5wvdgsytjwxcgxfv.jpeg" alt="Illustratie bij Why I Built a Halal-By-Design AI Image Pipeline" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trade-Offs (Be Honest)
&lt;/h2&gt;

&lt;p&gt;Three trade-offs to call out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. False positives.&lt;/strong&gt; "Yoga in a temple" gets sanitized to "yoga in a wellness". Not ideal. The replacement is graceful degradation, not perfect rewriting. For a brand-safety filter, false positives are cheaper than false negatives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Reduced creative range.&lt;/strong&gt; I cannot generate a Diwali-themed wellness post easily, because "diwali" is not in the blacklist but the visual will likely include diyas near deities. I would need to add it. The blacklist grows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Cultural blind spots.&lt;/strong&gt; My blacklist was built from a Western-Muslim perspective. A Hindu reader might find it incomplete. A Buddhist reader might find it over-zealous. I update it when I learn about gaps. This is not a finished product.&lt;/p&gt;

&lt;p&gt;The point. The filter is intentionally conservative. For a health platform serving a mixed audience, conservative is correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Used the Word "Halal" (Not "Modesty Filter")
&lt;/h2&gt;

&lt;p&gt;I could have called this &lt;code&gt;BRAND_SAFETY_BLACKLIST&lt;/code&gt; or &lt;code&gt;MODESTY_FILTER&lt;/code&gt;. I called it the halal filter because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It is more honest.&lt;/strong&gt; The filter was built with Islamic standards in mind. Naming it after a generic concept would have hidden the inspiration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It signals to my Muslim audience.&lt;/strong&gt; They know exactly what to expect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It opens a conversation with non-Muslim audiences.&lt;/strong&gt; "Halal" is a loaded word in the West. Using it provokes the right question: "Why would a tech platform need a halal filter?" The answer (brand-safety, universal compatibility) is the interesting part.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is a competitive moat.&lt;/strong&gt; Most platforms will not call their filter "halal" because they are afraid of the word. That fear creates space for brands that are not afraid.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If "halal" feels too charged for your context, call it the strictest filter or the universal filter. The concept does not care about the label. The hardcoded blacklist is what matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Would Build Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cultural-context detector:&lt;/strong&gt; Halal-only food imagery (no pork, no alcohol-prominent scenes).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modesty filter:&lt;/strong&gt; No bikinis, no underwear shots, no over-exposed bodies in editorial photography.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sensitivity-tunable filter:&lt;/strong&gt; Three modes (strict / standard / loose) per content category.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vision-LLM verification pass:&lt;/strong&gt; After Stable Diffusion returns the image, run a vision model that confirms compliance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The core insight, one more time. Build the filter you would want for your strictest customer. Everyone else gets a bonus.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href="https://longevityai.nl" rel="noopener noreferrer"&gt;Longevity AI&lt;/a&gt;. Visit the source for the live wellness platform and the full technical context.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href="https://longevityai.nl/blog/halal-by-design-ai-image-pipeline" rel="noopener noreferrer"&gt;Longevity AI&lt;/a&gt;. Visit the source for the full context, references and discussion.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>technical</category>
      <category>architecture</category>
      <category>ai</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>How I Built a Health-Content Autopilot for €12/Month</title>
      <dc:creator>holistis</dc:creator>
      <pubDate>Tue, 02 Jun 2026 12:58:46 +0000</pubDate>
      <link>https://dev.to/holistis/how-i-built-a-health-content-autopilot-for-eu12month-n72</link>
      <guid>https://dev.to/holistis/how-i-built-a-health-content-autopilot-for-eu12month-n72</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Originally published on &lt;a href="https://longevityai.nl/blog/how-i-built-a-health-content-autopilot-12-euro-month" rel="noopener noreferrer"&gt;longevityai.nl&lt;/a&gt;&lt;/strong&gt; — for full context, comments and related articles, visit the source.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  How I Built a Health-Content Autopilot for €12/Month
&lt;/h1&gt;

&lt;p&gt;I run &lt;a href="https://longevityai.nl" rel="noopener noreferrer"&gt;Longevity AI&lt;/a&gt;, a Dutch holistic health platform. The content side was killing me. Writing one good evidence-based blog took 4-6 hours. I had nine ziektebeelden (chronic conditions) to cover, in three languages. That's roughly 50 blogs per year I could realistically produce. Not enough.&lt;/p&gt;

&lt;p&gt;So I built an autopilot. Here is how it works, what it costs, and what I had to fight to keep it ethical.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Quality Health Content Does Not Scale
&lt;/h2&gt;

&lt;p&gt;Health content has a quality floor that most blog-automation systems crash into. You cannot just pull RSS feeds and rephrase them. You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Peer-reviewed sources&lt;/strong&gt; — citation from PubMed, EFSA, ClinicalTrials.gov&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance&lt;/strong&gt; — Dutch IGJ, EU MDR, AVG (GDPR) all apply&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three languages&lt;/strong&gt; — Dutch primary, English and French as syndication&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cover image + inline visuals&lt;/strong&gt; — readers do not engage with text walls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My first attempt used GPT-4o-mini + Flux + manual review. Per blog: ~€0.50. Per cycle (every 6 hours, 3-5 blogs): €1.50-2.50. Per month: €120-240. Fine, but bleeding for a solo operation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fflzcpyjda9lnjovyst2r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fflzcpyjda9lnjovyst2r.png" alt="Illustratie bij How I Built a Health-Content Autopilot for €12/Month" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture: 4 Scrapers, 1 Orchestrator, 1 Gate
&lt;/h2&gt;

&lt;p&gt;The autopilot has four input sources. Each one is a separate scraper that returns a normalized signal object:&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ContentSignal&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;sourceText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// article body&lt;/span&gt;
  &lt;span class="nl"&gt;sourceUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// canonical link&lt;/span&gt;
  &lt;span class="nl"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nl&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;ziektebeeldIfKnown&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The four scrapers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;BBC Health RSS&lt;/strong&gt; — keyword-filtered for supplement, treatment, nutrition, sleep, immune, gut, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NCBI PubMed eUtils&lt;/strong&gt; — per-ziektebeeld scientific queries, rate-limited to 3 req/sec&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EFSA RSS&lt;/strong&gt; — EU supplement approvals and health claims (high-trust EU regulator)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ClinicalTrials.gov REST&lt;/strong&gt; — 7-day rolling window of new trial registrations&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The orchestrator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;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;runNewsRadarCycle&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&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;getDb&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;db&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;bbcArticles&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;scrapeBBCHealthFeed&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;article&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;bbcArticles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;signalAlreadyExists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&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;ziektebeeld&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;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LAB_FOLDER_BY_ZIEKTEBEELD&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;studies&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;scanPubMedForZiektebeeld&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ziektebeeld&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;study&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;studies&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// dedup + insert&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// EFSA + ClinicalTrials same pattern&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deduplication is two-layer: URL-hash in the orchestrator and a query check in &lt;code&gt;signalAlreadyExists()&lt;/code&gt; against the database. Both layers are necessary because the same study can appear in PubMed and ClinicalTrials with different URLs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Compliance Gate (The Hardest Part)
&lt;/h2&gt;

&lt;p&gt;Health content in the EU is a minefield. The IGJ (Dutch healthcare inspectorate) regularly fines wellness sites for treatment claims. The MDR (medical device regulation) governs anything that sounds like diagnostic advice. So I built a hard gate:&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;complianceResult&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;runComplianceCheck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceText&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;isSafe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;complianceResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;verdict&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SAFE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isSafe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pending_review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// never auto-publishes&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pending_auto_blog&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// proceeds to blog generation&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The compliance check uses Claude to evaluate against a curated set of rules: no diagnostic claims, no treatment imperatives, no cure language, no medication-stop advice. Verdict is SAFE / WARN / BLOCK. Only SAFE proceeds to auto-publish.&lt;/p&gt;

&lt;p&gt;This matters for one specific feature: affiliate links. I only inject affiliate links into 100% SAFE articles. Anything that fails compliance goes to manual review. The disclosure is explicit:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Wat je hierboven leest, is gebaseerd op peer-reviewed wetenschap en officiële gezondheidsrapporten. Wij noemen supplementen en bloedtests alleen op basis van wetenschappelijk bewijs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Transparency over deception. I had to push back on my own design here — the temptation to just hide AI-generation is real. But that crosses a line I will not cross.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cost Reduction (90% in One Refactor)
&lt;/h2&gt;

&lt;p&gt;After running for a few weeks I looked at the bill: €180/month for content the audience read for 30 seconds each. Time to optimize.&lt;/p&gt;

&lt;p&gt;Three changes, each independent:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Was&lt;/th&gt;
&lt;th&gt;Now&lt;/th&gt;
&lt;th&gt;Savings&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LLM&lt;/td&gt;
&lt;td&gt;GPT-4o-mini&lt;/td&gt;
&lt;td&gt;Claude Haiku&lt;/td&gt;
&lt;td&gt;90% on LLM cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Images&lt;/td&gt;
&lt;td&gt;Flux (Together.ai)&lt;/td&gt;
&lt;td&gt;Stability AI&lt;/td&gt;
&lt;td&gt;70% on image cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cycles&lt;/td&gt;
&lt;td&gt;Every 6 hours&lt;/td&gt;
&lt;td&gt;Every 12 hours&lt;/td&gt;
&lt;td&gt;50% fewer API calls&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The LLM swap was the biggest win. Claude Haiku writes more naturally than GPT-4o-mini for long-form Dutch content. I had assumed the cheaper model would mean worse output. It does not. It means different output, and for technical-but-friendly health content, Haiku wins.&lt;/p&gt;

&lt;p&gt;The image swap was equally surprising. Stability AI's stable-diffusion-3-large produces images that are 80% as good as Flux for 30% of the cost. For inline blog images that readers glance at for 2 seconds, that trade is obvious.&lt;/p&gt;

&lt;p&gt;Cycle frequency was the easiest win. Going from 6 to 12 hours did not affect the freshness of the content the audience saw. PubMed studies do not become stale in 6 hours.&lt;/p&gt;

&lt;p&gt;Final cost: &lt;strong&gt;€12-20/month&lt;/strong&gt;. Same quality. Same pipeline. Same compliance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp5oautnzi9l8cf1d1qp3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp5oautnzi9l8cf1d1qp3.png" alt="Illustratie bij How I Built a Health-Content Autopilot for €12/Month" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Auto-Syndication: One Source, Three Platforms
&lt;/h2&gt;

&lt;p&gt;Once a blog is generated and published on longevityai.nl, the system checks if it has a technical tag. If yes, it auto-syndicates to Hashnode and Dev.to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lang&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;hasTechnicalTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tagsCsv&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nf"&gt;publishToHashnode&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="nf"&gt;publishToDevTo&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;canonical_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;longevityaiUrl&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="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;canonical_url&lt;/code&gt; parameter is critical: it tells Google that longevityai.nl is the original source. No duplicate-content penalty. The syndicated copies are tracked in the &lt;code&gt;social_posts&lt;/code&gt; table to prevent double-posting on retry.&lt;/p&gt;

&lt;p&gt;This blog you are reading right now? Originally written for longevityai.nl. Auto-posted to Hashnode and Dev.to via this exact pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Compliance is the moat.&lt;/strong&gt; Anyone can scrape RSS feeds and rephrase them. Few people can pass an IGJ audit on the output. Build the gate first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Cheaper models often write more naturally.&lt;/strong&gt; Haiku writes Dutch health content with less corporate fluff than GPT-4o-mini. Test before assuming.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Deduplication is two-layer or it is broken.&lt;/strong&gt; URL-hash + DB query check. Same article hits the same pipeline from multiple sources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Canonical URLs &amp;gt; hiding.&lt;/strong&gt; Syndicate openly with &lt;code&gt;canonical_url&lt;/code&gt;. Hiding AI-generation crosses an ethical line and Google's 2024+ guidelines do not penalize transparent AI content anymore.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Fire-and-forget syndication.&lt;/strong&gt; Hashnode/Dev.to failures should not break the main publish flow. Wrap in try/catch, log warnings, never crash.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Would Build Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LinkedIn syndication&lt;/strong&gt; (waiting on API approval)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Medium fallback&lt;/strong&gt; for health-content audience (not just technical)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Topic clustering&lt;/strong&gt; so the cron only generates content where my lab/RAG has expertise&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weekly digest emails&lt;/strong&gt; auto-generated from the past 7 days of new blogs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The core insight: do not try to compete on content volume. Compete on the depth of the pipeline behind each piece. Quality gates, transparent sourcing, ethical syndication. The autopilot is not about more blogs. It is about every blog being defensible.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href="https://longevityai.nl" rel="noopener noreferrer"&gt;Longevity AI&lt;/a&gt;. Visit the source for the full context, references and the live News Radar in action.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href="https://longevityai.nl/blog/how-i-built-a-health-content-autopilot-12-euro-month" rel="noopener noreferrer"&gt;Longevity AI&lt;/a&gt;. Visit the source for the full context, references and discussion.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>technical</category>
      <category>architecture</category>
      <category>ai</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>Three free wearable integrations for an AI health report: Oura, Withings, Polar</title>
      <dc:creator>holistis</dc:creator>
      <pubDate>Mon, 01 Jun 2026 13:20:22 +0000</pubDate>
      <link>https://dev.to/holistis/three-free-wearable-integrations-for-an-ai-health-report-oura-withings-polar-m9n</link>
      <guid>https://dev.to/holistis/three-free-wearable-integrations-for-an-ai-health-report-oura-withings-polar-m9n</guid>
      <description>&lt;h2&gt;
  
  
  Live today: three wearables, free to connect
&lt;/h2&gt;

&lt;p&gt;From today you can connect your Oura Ring, Withings device or Polar watch to your longevity report at &lt;a href="https://longevityai.nl" rel="noopener noreferrer"&gt;longevityai.nl&lt;/a&gt;. Free of charge after payment of the report, in a few clicks. No paid aggregator, direct OAuth2 with each manufacturer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;A questionnaire knows what you &lt;em&gt;think&lt;/em&gt; about your sleep. A ring knows how you &lt;em&gt;actually&lt;/em&gt; sleep.&lt;/p&gt;

&lt;p&gt;In a longevity report we combine two sources:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Subjective:&lt;/strong&gt; what you fill in during the intake. How you sleep, eat, move, feel. Complaints, patterns, intuitions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Objective:&lt;/strong&gt; what a wearable measures. HRV, resting heart rate, sleep stages, breathing rate. With Withings: weight, blood pressure, fat mass. With Polar: breathing frequency. With Oura: readiness score.&lt;/p&gt;

&lt;p&gt;The value is in the &lt;em&gt;correlation&lt;/em&gt; between the two. Not in either one alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Engineering notes
&lt;/h2&gt;

&lt;p&gt;Three different OAuth2 flows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Oura&lt;/strong&gt; (&lt;code&gt;cloud.ouraring.com/oauth/authorize&lt;/code&gt; + &lt;code&gt;api.ouraring.com/oauth/token&lt;/code&gt;): clean v2 REST API, JSON responses, standard refresh tokens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Withings&lt;/strong&gt; (&lt;code&gt;account.withings.com/oauth2_user/authorize2&lt;/code&gt; + &lt;code&gt;wbsapi.withings.net/v2/oauth2&lt;/code&gt;): every API response wrapped in &lt;code&gt;{status: 0, body: {...}}&lt;/code&gt;. Token endpoint expects &lt;code&gt;action=requesttoken&lt;/code&gt; in POST body, not just &lt;code&gt;grant_type&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Polar&lt;/strong&gt; (&lt;code&gt;flow.polar.com/oauth2/authorization&lt;/code&gt; + &lt;code&gt;polarremote.com/v3/oauth2/token&lt;/code&gt;): Basic Auth header for token exchange (base64 of client_id:client_secret). Requires a separate &lt;code&gt;POST /v3/users&lt;/code&gt; call after token-exchange to register the user with member-id, otherwise all data endpoints return 403.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each has its own token-refresh strategy. Withings tokens expire in hours, Oura in days, Polar tokens are long-lived without refresh endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  AVG-compliant data pipeline
&lt;/h2&gt;

&lt;p&gt;We only store a summary. No raw nightly records. Disconnect from the user dashboard, all derived data is purged. GDPR article 9 (special-category data) covered through explicit consent at intake.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trilingual launch
&lt;/h2&gt;

&lt;p&gt;Available immediately in Dutch, English and French. Translation-group system in the database links the language-versions of each article so a language-switch on a blog page never lands on a 404 again.&lt;/p&gt;

&lt;p&gt;Read the full story (Dutch original):&lt;br&gt;
&lt;a href="https://longevityai.nl/blog/wearables-gekoppeld-aan-anamnese-oura-withings-polar" rel="noopener noreferrer"&gt;https://longevityai.nl/blog/wearables-gekoppeld-aan-anamnese-oura-withings-polar&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;English version:&lt;br&gt;
&lt;a href="https://longevityai.nl/blog/wearables-linked-to-intake-oura-withings-polar" rel="noopener noreferrer"&gt;https://longevityai.nl/blog/wearables-linked-to-intake-oura-withings-polar&lt;/a&gt;&lt;/p&gt;

</description>
      <category>oauth2</category>
      <category>healthtech</category>
      <category>wearables</category>
      <category>api</category>
    </item>
  </channel>
</rss>
