<?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: slawekluzny</title>
    <description>The latest articles on DEV Community by slawekluzny (@slawekluzny).</description>
    <link>https://dev.to/slawekluzny</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%2F3948082%2Fccd62e26-a359-45f9-8d49-54ce98ab7ba9.png</url>
      <title>DEV Community: slawekluzny</title>
      <link>https://dev.to/slawekluzny</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/slawekluzny"/>
    <language>en</language>
    <item>
      <title>How the Ledger Module Actually Works: SQLite, Regex, and Avoiding LLM Maths</title>
      <dc:creator>slawekluzny</dc:creator>
      <pubDate>Tue, 13 Oct 2026 08:00:06 +0000</pubDate>
      <link>https://dev.to/slawekluzny/how-the-ledger-module-actually-works-sqlite-regex-and-avoiding-llm-maths-59gj</link>
      <guid>https://dev.to/slawekluzny/how-the-ledger-module-actually-works-sqlite-regex-and-avoiding-llm-maths-59gj</guid>
      <description>&lt;h1&gt;
  
  
  How the Ledger Module Actually Works: SQLite, Regex, and Avoiding LLM Maths
&lt;/h1&gt;

&lt;p&gt;We built Kep's Ledger module with accountants in mind, but it's useful for anyone who needs to work with financial figures in documents without pasting them into a cloud chatbot. Here's the technical reality under the hood — no fluff, just the facts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Problem: LLMs Shouldn't Do the Adding Up
&lt;/h2&gt;

&lt;p&gt;Language models are good at reading and explaining. They are not reliable calculators: ask one to total a column of figures from a document and it may well give you a confident, plausible and wrong number. For accounting work that's not a quirk, it's a liability.&lt;/p&gt;

&lt;p&gt;So Ledger never asks the model to do the arithmetic. It runs two paths side by side:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Semantic RAG&lt;/strong&gt; (retrieval-augmented generation) for questions about meaning — "explain this VAT treatment".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic SQL sums&lt;/strong&gt; for the numbers themselves.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When a document is added to the Ledger module:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Extract&lt;/strong&gt; the text (PDFs via &lt;code&gt;pypdf&lt;/code&gt;, the same ingest path as every other module).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parse&lt;/strong&gt; amounts with a deliberately conservative regex — &lt;code&gt;£1,234.56&lt;/code&gt;, &lt;code&gt;GBP 1,234.56&lt;/code&gt;, &lt;code&gt;1,234.56 GBP&lt;/code&gt; — together with the line they appear on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Store&lt;/strong&gt; each amount as integer pence in its own SQLite table.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When you ask a question in a Ledger conversation, Kep sums those stored amounts in SQL — across the Ledger documents you're allowed to see — and hands the model a block like this alongside the retrieved text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Computed totals (from 14 structured record(s), computed exactly — not by you):
- Sum: £7,407.36 GBP
- ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The module's system prompt tells the model to treat that block as authoritative over any figure it reads out of the documents, while still explaining the numbers in plain language. The model reads and explains; SQL does the maths.&lt;/p&gt;

&lt;h2&gt;
  
  
  No Cloud, No Shared Anything
&lt;/h2&gt;

&lt;p&gt;The security model is the same as the rest of Kep:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Physical isolation:&lt;/strong&gt; each firm has its own machine running its own local model — no shared server, no shared GPU time, nothing wired between one client and another.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict ownership:&lt;/strong&gt; even within a firm, one person can't see another's documents unless they're explicitly shared. The Ledger totals go through exactly the same visibility rule as chat and search (&lt;code&gt;visible_document_ids()&lt;/code&gt;), so a sum can never include a colleague's private figures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ledger uses the same SQLite + &lt;code&gt;sqlite-vec&lt;/code&gt; setup as everything else because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It works fully offline.&lt;/li&gt;
&lt;li&gt;Backup is copying one file — no database server to run.&lt;/li&gt;
&lt;li&gt;There's nothing extra to install, secure or keep patched.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  One Codebase, Modules Switched On by Licence
&lt;/h2&gt;

&lt;p&gt;Every Kep install has the same code. Industry modules (Ledger, Clause and others) ship inert and are switched on by a licence grant from our licensing service — not by installing a different build. The registry lives in &lt;code&gt;modules/base.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The one exception is Forge, our internal development module: it only registers itself when &lt;code&gt;node_type == "internal_dev"&lt;/code&gt;, so a client install never sees it at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;For regulated work, AI isn't about flashy demos — it's about numbers you can check and a system you can explain to an auditor. That's why the Ledger module:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;relies on regex and integer arithmetic for figures,&lt;/li&gt;
&lt;li&gt;never lets the model invent or recalculate a total,&lt;/li&gt;
&lt;li&gt;keeps its data in a single local SQLite file on the firm's own machine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's not revolutionary. It just works without asking anyone to put blind faith in a black box — and for accountants, that's the whole point.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Footnotes for the especially pedantic&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Yes, the regex won't catch every format. Anything it can't match confidently is left out of the totals rather than guessed — the figure is still there in the document for the model to quote and for you to check.&lt;/li&gt;
&lt;li&gt;Amounts are stored as integer pence to avoid floating-point drift, and only formatted as pounds for display.&lt;/li&gt;
&lt;li&gt;Today the totals cover all Ledger documents you can see. Filtering them by date or category ("expenses last quarter") is not built yet.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Kep is built by FixFlex Ltd, London. Try the online demo — a fictional firm, no sign-up — at &lt;a href="https://kep.fixflex.co.uk/" rel="noopener noreferrer"&gt;kep.fixflex.co.uk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>accounting</category>
      <category>localai</category>
      <category>sqlite</category>
      <category>regex</category>
    </item>
    <item>
      <title>How Sentinel Shares Threat Intelligence Across Your Servers</title>
      <dc:creator>slawekluzny</dc:creator>
      <pubDate>Sun, 04 Oct 2026 21:16:07 +0000</pubDate>
      <link>https://dev.to/slawekluzny/how-sentinel-shares-threat-intelligence-across-your-servers-ipk</link>
      <guid>https://dev.to/slawekluzny/how-sentinel-shares-threat-intelligence-across-your-servers-ipk</guid>
      <description>&lt;h1&gt;
  
  
  How Sentinel Shares Threat Intelligence Across Your Servers
&lt;/h1&gt;

&lt;p&gt;Most server security tools work alone. Each machine bans the attackers it sees, and the next machine has to learn about the same attacker from scratch. Sentinel's approach is to let every protected server learn from the others. Here's how that works, and what Sentinel does (and deliberately doesn't do) automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  The building blocks on each server
&lt;/h2&gt;

&lt;p&gt;Sentinel's security autopilot integrates with three tools that do the actual blocking on every server: &lt;strong&gt;Fail2Ban&lt;/strong&gt; (jail management), &lt;strong&gt;CrowdSec&lt;/strong&gt; (threat intelligence and an HTTP guard) and &lt;strong&gt;UFW&lt;/strong&gt; (firewall rule monitoring).&lt;/p&gt;

&lt;p&gt;On top of that, Sentinel detects which services are running and automatically installs Fail2Ban jails for them: &lt;strong&gt;sshd, nginx, caddy, apache and mysqld&lt;/strong&gt;. For MySQL, that means failed logins are caught by a dedicated &lt;code&gt;mysqld&lt;/code&gt; jail instead of being left unprotected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross-Fleet IP Reputation: one server's attacker is every server's attacker
&lt;/h2&gt;

&lt;p&gt;This is where the fleet comes in:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Each server reports the IPs it has banned to &lt;strong&gt;Queen&lt;/strong&gt;, Sentinel's central server.&lt;/li&gt;
&lt;li&gt;Queen correlates those reports &lt;strong&gt;every hour, over the last 7 days&lt;/strong&gt;, and publishes known attackers as IP-reputation entries.&lt;/li&gt;
&lt;li&gt;On their next sync with Queen, the other servers in your fleet &lt;strong&gt;ban those IPs too&lt;/strong&gt;, before they've tried anything there.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So an IP that hammers SSH on Server A gets blocked on Server B without Server B having to see a single failed login. Cross-Fleet IP Reputation is part of the &lt;strong&gt;Enterprise&lt;/strong&gt; tier ($149/month, up to 10 servers).&lt;/p&gt;

&lt;h2&gt;
  
  
  What stays a human decision
&lt;/h2&gt;

&lt;p&gt;Some actions are powerful enough that Sentinel leaves them to you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Subnet bans.&lt;/strong&gt; Sentinel does not automatically block whole ranges. If you decide a /24 or /16 needs banning, an admin can do it in Fail2Ban through the AI Chat tool &lt;code&gt;ban_subnet&lt;/code&gt; (Enterprise), as a manual, operator-approved action.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP floods&lt;/strong&gt; are handled by the &lt;strong&gt;Rate Limiting / Active Shield&lt;/strong&gt; feature (Pro and above), not by the uptime monitor.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Beyond attacks: certificates
&lt;/h2&gt;

&lt;p&gt;Not every risk is an attacker knocking on the door. Sentinel also monitors SSL certificates and alerts you before they expire, so a forgotten renewal doesn't turn into downtime.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Sentinel AI&lt;/strong&gt; is available in three tiers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Basic:&lt;/strong&gt; Free for 1 production server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pro:&lt;/strong&gt; $49/month for up to 5 servers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise:&lt;/strong&gt; $149/month for up to 10 servers, with AI Chat, AI Healer and Cross-Fleet IP Reputation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sign up at &lt;a href="https://sentinel-ai.info/pricing" rel="noopener noreferrer"&gt;sentinel-ai.info/pricing&lt;/a&gt; to receive a personalised one-line install command (installation takes about 2–4 minutes).&lt;/p&gt;

&lt;p&gt;Built by &lt;strong&gt;FixFlex Ltd&lt;/strong&gt;, a London-based AI and web studio.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How AI Became My Dev Team (And Why I Still Make The Decisions)</title>
      <dc:creator>slawekluzny</dc:creator>
      <pubDate>Sun, 04 Oct 2026 18:48:23 +0000</pubDate>
      <link>https://dev.to/slawekluzny/how-ai-became-my-dev-team-and-why-i-still-make-the-decisions-52bh</link>
      <guid>https://dev.to/slawekluzny/how-ai-became-my-dev-team-and-why-i-still-make-the-decisions-52bh</guid>
      <description>&lt;h1&gt;
  
  
  How AI Became My Dev Team (And Why I Still Make The Decisions)
&lt;/h1&gt;

&lt;p&gt;I run a construction company in West London. I also build software: a classifieds platform, a server-security tool, a couple of content tools. I am not a programmer in the way people usually mean it. I can't write most of that code from memory, and I've stopped pretending that I need to.&lt;/p&gt;

&lt;p&gt;My working rule fits on one line: &lt;strong&gt;I imagine, describe, verify. AI codes. I decide.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This post is about what that actually looks like day to day: where AI genuinely does the work, and where I refuse to let it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it started: paying for code I couldn't read
&lt;/h2&gt;

&lt;p&gt;My first attempt at software was the usual one. I bought a ready-made classifieds script and hired freelancers to customise it. Then more freelancers, when the first ones disappeared or delivered something that half-worked.&lt;/p&gt;

&lt;p&gt;The problem wasn't that they were bad people. The problem was that I couldn't judge their work. If you can't read code, a clean, maintainable feature and a duct-taped one look exactly the same: a button that does what it's supposed to do, until the day it doesn't.&lt;/p&gt;

&lt;p&gt;That's the trap AI could easily recreate. If I just accept whatever it writes, I'm back where I started, only faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rewrite: fast to build, slow to finish
&lt;/h2&gt;

&lt;p&gt;The clearest example is 24ad.info, the classifieds platform. It started life on a Laravel codebase inherited from freelancers, one I never fully understood. Since February 2026 it runs on a stack rebuilt with Claude Code: React 19 on the front end, Express and tRPC on the back end, MySQL underneath.&lt;/p&gt;

&lt;p&gt;I'd like to tell you the rewrite was a heroic all-nighter that fixed everything. It wasn't. The honest version is more useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The rewrite itself was fast.&lt;/strong&gt; Describing what a page or an API should do, and having working code minutes later, really does collapse the time between idea and result.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The polishing took much longer.&lt;/strong&gt; Edge cases, translations, payments, the small things real users hit. Some gaps and small bugs are still being fixed today.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That second point is the part most "I built X with AI" stories leave out. AI makes the first 80% cheap. The last 20% still needs someone who cares whether it's right.&lt;/p&gt;

&lt;h2&gt;
  
  
  Describing, not dictating
&lt;/h2&gt;

&lt;p&gt;What changed my results wasn't a better model. It was learning to describe things properly:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;What exists now.&lt;/strong&gt; "Listings are created on a country subdomain, and the currency is set from that country."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What needs to exist.&lt;/strong&gt; "The price field should be pre-filled, but always editable."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How I'll know it works.&lt;/strong&gt; "On the Polish subdomain a new listing shows PLN, and changing it by hand sticks."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Vague requests get confident, plausible, wrong answers. Specific ones get code I can actually check.&lt;/p&gt;

&lt;h2&gt;
  
  
  The verification habit
&lt;/h2&gt;

&lt;p&gt;"Verify" is the part of the rule that does the real work, so it gets its own tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Project memory.&lt;/strong&gt; Every project I work on has a plain-Markdown map of what's done, what failed and what's next, so an AI session starts from facts, not guesses. Something marked verified doesn't get silently redone; something marked failed doesn't get quietly suggested again. I open-sourced it as Project Brain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fact-checking my own content.&lt;/strong&gt; Even the articles on this blog go through a check. When a draft makes a claim about one of my products, a Claude Code instance running on that product's own server checks it against the real code and data before anything is published. That's not hypothetical: it has caught drafts confidently describing features and incidents that never existed. Those got cut.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A human publishes.&lt;/strong&gt; Nothing ships to a live site, and no article goes out, without me deciding it should.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where I keep AI on a short leash
&lt;/h2&gt;

&lt;p&gt;Some decisions stay mine, every time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What to build, and why.&lt;/strong&gt; AI is excellent at "how". It has no idea what my users actually struggle with on a Tuesday morning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anything involving money or customer data.&lt;/strong&gt; Payments, licences, personal data: these are the places I slow down most and check hardest before anything goes live.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When "technically correct" is still wrong.&lt;/strong&gt; AI sometimes proposes a solution that works and still feels off: too clever, too heavy, solving a problem I don't have. Learning to say "no, simpler" was one of the most valuable skills I picked up.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What this actually adds up to
&lt;/h2&gt;

&lt;p&gt;AI didn't replace a development team for me. It made it possible to have one at all: a collaborator that writes most of the code, explains it when I ask why, and never gets tired of my questions.&lt;/p&gt;

&lt;p&gt;But the responsibility didn't move. If something breaks on 24ad.info at 3am, it's still my name on it. That's exactly why I still make the decisions.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built by FixFlex Ltd, West London. If you're a non-technical founder wondering whether you can build real products this way: you can. Just don't skip the "verify" part.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>programming</category>
      <category>software</category>
    </item>
    <item>
      <title>Why Kep Runs Entirely on Ollama: The Architecture Behind Local-Only AI for Regulated Industries</title>
      <dc:creator>slawekluzny</dc:creator>
      <pubDate>Sat, 03 Oct 2026 09:02:30 +0000</pubDate>
      <link>https://dev.to/slawekluzny/why-kep-runs-entirely-on-ollama-the-architecture-behind-local-only-ai-for-regulated-industries-24m2</link>
      <guid>https://dev.to/slawekluzny/why-kep-runs-entirely-on-ollama-the-architecture-behind-local-only-ai-for-regulated-industries-24m2</guid>
      <description>&lt;h1&gt;
  
  
  Why Kep Runs Entirely on Ollama: The Architecture Behind Local-Only AI for Regulated Industries
&lt;/h1&gt;

&lt;p&gt;Kep is a private AI assistant for law firms and accountants that runs on a dedicated machine in the client's own office. Every architectural decision behind it follows from one rule: a client install only ever talks to a model running in the room it's sitting in. Here's how that rule shapes the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Design Constraint
&lt;/h2&gt;

&lt;p&gt;This isn't a philosophical stance against cloud AI. It's a response to the position regulated firms are in:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;They answer to clients, insurers and auditors who increasingly ask one question about any AI tool: &lt;em&gt;where do our documents go?&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;"To a third-party model provider, under a data-processing agreement" is a defensible answer — but it is still a promise, not an architecture.&lt;/li&gt;
&lt;li&gt;Accounting work needs exact figures. "Approximately" isn't an acceptable answer when the numbers end up in a tax return.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So the rule is enforced in code, not in a policy document. In &lt;code&gt;llm/__init__.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CloudBackendNotAllowed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_effective_provider&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;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;llm_provider&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openrouter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;node_type&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;internal_dev&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;CloudBackendNotAllowed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;node_type=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;node_type&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="s"&gt; may not use provider=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="s"&gt;. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Only node_type=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;internal_dev&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; may use a cloud LLM backend.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default &lt;code&gt;node_type&lt;/code&gt; is &lt;code&gt;kep_node&lt;/code&gt; — anything installed at a client. A client machine that is misconfigured to point at a cloud provider doesn't quietly fall back to it; it refuses to start generating.&lt;/p&gt;

&lt;p&gt;The one place a cloud model is allowed is an internal instance — which is exactly what our public online demo is. It runs a fictional law firm on a cloud model so anyone can try Kep from a browser, and it says so in a banner on every page. A real Kep never does.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hardware Reality
&lt;/h2&gt;

&lt;p&gt;One dedicated machine per client solves three problems at once:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;Cloud Approach&lt;/th&gt;
&lt;th&gt;Kep Approach&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Data residency&lt;/td&gt;
&lt;td&gt;Multi-region deployment, data-processing agreements&lt;/td&gt;
&lt;td&gt;Physical hardware inside the client's own office&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audit trail&lt;/td&gt;
&lt;td&gt;Logs scattered across providers&lt;/td&gt;
&lt;td&gt;A single SQLite file the client can back up themselves&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance ceiling&lt;/td&gt;
&lt;td&gt;Per-seat API quotas&lt;/td&gt;
&lt;td&gt;One client = one entire machine's resources&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Ollama-only rule closes two doors: documents can't leak through a provider's SDK, and a model can't change behaviour mid-contract because a provider updated it. New models ship as a signed package installed on-site; the machine verifies the signature before applying it. The only network traffic is a small outbound health-and-licence check — never documents, questions or answers.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Retrieval Actually Works
&lt;/h2&gt;

&lt;p&gt;Most RAG systems trust the model to decide which documents it should even look at. For legal work that's not good enough: retrieval itself has to be scoped by ownership, before the model ever gets involved.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The query is embedded locally.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sqlite-vec&lt;/code&gt; searches only documents the requester owns or has been explicitly given access to (&lt;code&gt;visible_document_ids()&lt;/code&gt; in &lt;code&gt;brain/retrieve.py&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Only that already-filtered result set is handed to the model.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The model can't leak a colleague's private document because it never receives it. &lt;code&gt;test_documents.py&lt;/code&gt; covers this from the outside — for example, a document one user uploads is invisible to another user unless it's explicitly shared.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ledger's Dirty Secret
&lt;/h2&gt;

&lt;p&gt;The accounting module deliberately doesn't trust the model with arithmetic. When a document is ingested:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A conservative regex extracts amounts (&lt;code&gt;£1,234.56&lt;/code&gt;, &lt;code&gt;GBP 1,234.56&lt;/code&gt;, &lt;code&gt;1,234.56 GBP&lt;/code&gt;) with their line items. Anything it can't match with confidence is simply not extracted, rather than guessed.&lt;/li&gt;
&lt;li&gt;Amounts are stored as integer pence in their own SQLite table — no floating-point drift.&lt;/li&gt;
&lt;li&gt;A chat query gets the semantic RAG results &lt;em&gt;and&lt;/em&gt; a deterministic block summed in SQL, scoped by the same ownership rule as everything else:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Computed totals (from 14 structured record(s), computed exactly — not by you):
- Sum: £7,407.36 GBP
- ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The module's system prompt tells the model to treat that block as authoritative over any figure it reads out of retrieved text.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Single-Process Bottleneck
&lt;/h2&gt;

&lt;p&gt;Running Flask in production with a single worker sounds unusual until you consider the actual shape of the load: one firm, one machine, not a thousand paralegals hammering it at once. The semaphore that queues LLM generation in &lt;code&gt;api/chat.py&lt;/code&gt; is process-local, so it only means something in a single process — which is why the service is configured as gunicorn with &lt;code&gt;--workers 1 --threads N&lt;/code&gt;, on purpose. Chat streaming reuses the SSE pattern we'd already proven in Sentinel, which is part of why the stack is Flask rather than FastAPI.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Had to Sacrifice
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Instant model updates&lt;/strong&gt; — a signed on-site package means models change on a maintenance schedule, not the moment a new one drops. For a firm that needs the tool to behave the same way next month, that's a feature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontier-level raw capability&lt;/strong&gt; — local open models are very good, but the biggest cloud models still lead. In our own small test (one sample contract with 12 planted problems) the model Kep runs found 11 and the cloud model we compared it with found 10 — one test, not a benchmark.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scale illusions&lt;/strong&gt; — this doesn't scale horizontally by design. Each new client is another machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interface polish&lt;/strong&gt; — the web UI and the Word/Excel panel are the product for now; a native menu-bar app and voice input aren't built yet.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of that is an accident. It's the direct cost of the one rule everything else follows.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Kep is built by FixFlex Ltd, London. Read the non-technical story in &lt;a href="https://kep.fixflex.co.uk/blog/post.html?slug=why-we-built-an-ai-that-never-touches-the-cloud" rel="noopener noreferrer"&gt;Why we built an AI that never touches the cloud&lt;/a&gt;, or try the online demo — a fictional law firm, no sign-up — at &lt;a href="https://kep.fixflex.co.uk/" rel="noopener noreferrer"&gt;kep.fixflex.co.uk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ollama</category>
      <category>localai</category>
      <category>rag</category>
      <category>sqlitevec</category>
    </item>
    <item>
      <title>Inside BreathTime’s Flagship Petition: A Global Day of Breath, Once a Month</title>
      <dc:creator>slawekluzny</dc:creator>
      <pubDate>Thu, 24 Sep 2026 00:12:48 +0000</pubDate>
      <link>https://dev.to/slawekluzny/inside-breathtimes-flagship-petition-a-global-day-of-breath-once-a-month-1oi7</link>
      <guid>https://dev.to/slawekluzny/inside-breathtimes-flagship-petition-a-global-day-of-breath-once-a-month-1oi7</guid>
      <description>&lt;h1&gt;
  
  
  Inside BreathTime’s Flagship Petition: A Global Day of Breath, Once a Month
&lt;/h1&gt;

&lt;p&gt;This morning, I opened BreathTime’s admin panel to check on the platform’s first major petition: &lt;em&gt;BreathTime – globalny dzień oddechu dla naszej planety&lt;/em&gt; (BreathTime — a global day of breath for our planet). It’s an ambitious idea. &lt;strong&gt;Once a month, on the second Sunday, pause and let the planet breathe.&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;At the time of writing, it has 8 signatures. The goal? 1,000. A modest start, sure, but this isn’t just another petition. It’s a test of BreathTime’s core philosophy: &lt;strong&gt;cooperation over disruption, grassroots action over top-down regulation.&lt;/strong&gt;  &lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;If you’ve followed climate activism over the past decade, you’ll have noticed a growing tension. Governments struggle to implement meaningful policies fast enough. Corporations face pressure to decarbonise but are often accused of greenwashing. And activists? They’re increasingly resorting to disruptive tactics — blocking roads, targeting art, staging dramatic protests — to draw attention to the crisis.  &lt;/p&gt;

&lt;p&gt;Those tactics work in one sense: they get headlines. But they also polarise. For every supporter they win, they likely alienate someone else. Worse, they often fail to translate visibility into concrete action.  &lt;/p&gt;

&lt;p&gt;Meanwhile, the clock is ticking.  &lt;/p&gt;




&lt;h2&gt;
  
  
  The Proposal
&lt;/h2&gt;

&lt;p&gt;Enter BreathTime’s flagship petition. It’s &lt;strong&gt;not another emissions-trading scheme or complex regulation&lt;/strong&gt;. It’s a simple, grassroots idea: &lt;strong&gt;once a month, pause.&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;The asks are straightforward:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pause commercial flights.
&lt;/li&gt;
&lt;li&gt;Reduce combustion-vehicle traffic (promote public transport and cycling instead).
&lt;/li&gt;
&lt;li&gt;Reduce industrial production (except in critical sectors) and use the pause for maintenance and modernisation.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But the petition goes beyond just a list of asks. It frames itself as a &lt;strong&gt;constructive alternative&lt;/strong&gt; to disruptive protest.  &lt;/p&gt;

&lt;p&gt;No blocking roads. No vandalising art. No aggressive demonstrations. Instead, the focus is on organising local educational events, joint environmental actions, and building ecological awareness.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Why Cooperation Over Disruption?
&lt;/h2&gt;

&lt;p&gt;The petition’s positioning is deliberate. It’s &lt;strong&gt;not anti-activism&lt;/strong&gt;. It’s anti-disruption. The idea is to channel activist energy into organising BreathTime-day events and dialogue with authorities and businesses.  &lt;/p&gt;

&lt;p&gt;For governments, the pitch is pragmatic:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Economically sensible&lt;/strong&gt;: No new costly infrastructure required. Potential healthcare savings from cleaner air.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Socially acceptable&lt;/strong&gt;: Shared action instead of new taxes or fines.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Politically low-risk&lt;/strong&gt;: No protests against new taxes. Positive image from supporting a grassroots initiative.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy to implement&lt;/strong&gt;: Simple rules that can scale gradually.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For businesses, the argument is equally practical. A global day of breath isn’t about shutting down industries permanently. It’s about using the pause for maintenance and modernisation — a chance to prepare for a greener future without the pressure of daily production.  &lt;/p&gt;

&lt;p&gt;For individuals, it’s an opportunity to participate in something concrete and community-driven. Sign the petition. Share the idea. Get involved locally. Become an ambassador.  &lt;/p&gt;




&lt;h2&gt;
  
  
  The Platform
&lt;/h2&gt;

&lt;p&gt;BreathTime, the platform, is an early-stage product. Version 0.2.2, to be exact. It’s not a social media site or a general news platform. It’s a place for civic petitions and social initiatives — a space where ideas like the global day of breath can take root.  &lt;/p&gt;

&lt;p&gt;Technically, it’s built on Next.js 16 (App Router) with TypeScript. The homepage features a Three.js-animated 3D globe: &lt;strong&gt;yellow dots&lt;/strong&gt; represent registered users by IP geolocation, &lt;strong&gt;green&lt;/strong&gt; shows the current logged-in user, and &lt;strong&gt;red&lt;/strong&gt; marks guests. The UI supports 52 languages, with interface strings AI-translated via OpenRouter/GPT-4o mini — that translation layer covers labels and buttons only, not blog post content.  &lt;/p&gt;

&lt;p&gt;There’s also an e-book library (PDF/EPUB/MOBI), in-app notifications, a user dashboard, and admin panels for managing users, petitions, proposals, e-books, and languages.  &lt;/p&gt;

&lt;p&gt;Hosting-wise, it runs on a separate VPS from ArticlePilot itself, with PM2 cluster (2 processes, port 3006), Caddy reverse proxy, and auto SSL.  &lt;/p&gt;




&lt;h2&gt;
  
  
  The Challenges
&lt;/h2&gt;

&lt;p&gt;Of course, the petition isn’t perfect. Some will argue that once a month isn’t enough. Others will point out the logistical challenges of pausing commercial flights or reducing industrial production.  &lt;/p&gt;

&lt;p&gt;But that’s the beauty of BreathTime’s approach. It’s a &lt;strong&gt;starting point&lt;/strong&gt;, not a final solution. The goal isn’t to solve climate change overnight. It’s to create a framework for collective action — a structure where individuals, businesses, and governments can work together towards a shared goal.  &lt;/p&gt;




&lt;h2&gt;
  
  
  The Future
&lt;/h2&gt;

&lt;p&gt;As of today, the petition has 8 signatures. The road to 1,000 will be long. But every signature is a vote for cooperation over disruption, for dialogue over confrontation.  &lt;/p&gt;

&lt;p&gt;If you’re reading this, I encourage you to &lt;strong&gt;sign the petition&lt;/strong&gt;. Share it with friends. Organise an event in your community. Become an ambassador.  &lt;/p&gt;

&lt;p&gt;The global day of breath might not solve climate change. But it’s a step in the right direction — and a reminder that sometimes, the simplest ideas can have the biggest impact.  &lt;/p&gt;




&lt;p&gt;&lt;em&gt;BreathTime is an early-stage platform for civic petitions and social initiatives.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Building a Multilingual AI-Powered Classifieds Platform</title>
      <dc:creator>slawekluzny</dc:creator>
      <pubDate>Sat, 15 Aug 2026 23:26:04 +0000</pubDate>
      <link>https://dev.to/slawekluzny/building-a-multilingual-ai-powered-classifieds-platform-2hm0</link>
      <guid>https://dev.to/slawekluzny/building-a-multilingual-ai-powered-classifieds-platform-2hm0</guid>
      <description>&lt;h3&gt;
  
  
  Building a Multilingual AI-Powered Classifieds Platform
&lt;/h3&gt;

&lt;p&gt;This morning, I woke up to a notification from 24ad.info about a bug in the payments flow. It reminded me of the journey this platform has taken since its inception — from a simple idea to a fully-fledged AI-powered classifieds system. Let’s dive into the technical details and lessons learned along the way.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Problem: Making Classifieds Effortless
&lt;/h4&gt;

&lt;p&gt;Classifieds platforms have been around forever, but they’re often clunky. Users spend forever writing descriptions, picking categories, and setting prices. What if we could automate all of that? That’s the core idea behind 24ad.info: upload a photo, and let AI handle the rest.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Tech Stack
&lt;/h4&gt;

&lt;p&gt;Here’s what powers 24ad.info:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: React 19 with Vite for blazing-fast builds, Tailwind CSS for utility-first styling, and shadcn/ui for polished components. Routing is handled by wouter, and tRPC v11 manages the API communication.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend&lt;/strong&gt;: Node.js with Express for the server, tRPC v11 for type-safe API endpoints, and Drizzle ORM for database interactions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database&lt;/strong&gt;: MySQL, hosted on managed internal infrastructure (exact host name withheld), handles everything from user data to post metadata.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI&lt;/strong&gt;: OpenRouter provides access to models like Google Gemini 2.5 Flash for vision tasks and Gemini 2.0 Flash for text tasks (translations, graphics prompts).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth&lt;/strong&gt;: Google OAuth and email/password authentication (JWT-based) keep things secure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payments&lt;/strong&gt;: Stripe handles checkout and webhooks for premium listings and packages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web Server&lt;/strong&gt;: Caddy v2 manages wildcard subdomains, HSTS, and compression (zstd+gzip).&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  The AI Features
&lt;/h4&gt;

&lt;p&gt;The AI-powered features are the heart of 24ad.info:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Photo Analysis&lt;/strong&gt;: Upload a photo, and OpenRouter’s Google Gemini 2.5 Flash model analyzes it to recognize the item, assess its condition, and suggest a price. This is the first step in automating the listing process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Category Classification&lt;/strong&gt;: The AI matches the item to the most relevant category. If the category is inactive, it reactivates it instead of creating a duplicate — keeping the catalog clean.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Description Generation&lt;/strong&gt;: SEO-optimized descriptions are generated automatically, saving users time and improving search visibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smart Pricing&lt;/strong&gt;: Based on market data and the photo analysis, the AI suggests a competitive price.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Package Translation&lt;/strong&gt;: For listings that span multiple countries, the AI translates the title and description.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These features collectively automate the listing process.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Payment Bug
&lt;/h4&gt;

&lt;p&gt;One of the more embarrassing bugs we encountered was in the payments flow. After a successful Stripe payment, the promotion or package wasn’t being applied to the post. The &lt;code&gt;verifyCheckoutSession&lt;/code&gt; endpoint marked the payment as completed but never called &lt;code&gt;applyPackageToPost&lt;/code&gt;. This meant users paid for premium features but didn’t get them.&lt;/p&gt;

&lt;p&gt;The fix, shipped in v1.4.4 on 2026-05-20, was straightforward: we added the &lt;code&gt;applyPackageToPost&lt;/code&gt; step to the checkout verification flow. We also added an admin bypass to manually activate packages for testing purposes. Lesson learned: always test edge cases thoroughly, especially when money is involved.&lt;/p&gt;

&lt;h4&gt;
  
  
  Search and Location
&lt;/h4&gt;

&lt;p&gt;Searching for listings is a core feature of any classifieds platform. Here’s how we handle it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Keyword Search&lt;/strong&gt;: Full-text search across titles and descriptions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Category Filters&lt;/strong&gt;: Narrow results by category and custom fields.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Price Range&lt;/strong&gt;: Filter by minimum and maximum price.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Location&lt;/strong&gt;: Search by city or postcode, with a radius slider (1-100 km) for nearby listings.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The location search is powered by a single-pass SQL query that resolves city names or postcodes to city IDs. A separate radius-search procedure finds nearby cities by distance. There’s no multi-step funnel or full-text fallback — keeping the search fast and efficient.&lt;/p&gt;

&lt;h4&gt;
  
  
  Image Optimization
&lt;/h4&gt;

&lt;p&gt;All image uploads are converted to WebP on the fly using sharp (quality 82, effort 4). This reduces file sizes significantly. The v1.4.2 update batch-converted existing images to WebP, ensuring consistency across the platform.&lt;/p&gt;

&lt;h4&gt;
  
  
  Infrastructure and Scaling
&lt;/h4&gt;

&lt;p&gt;Caddy v2 handles the web server duties, including wildcard subdomains, HSTS, and compression. The &lt;code&gt;encode zstd gzip&lt;/code&gt; directive ensures all responses are compressed, regardless of file type or size.&lt;/p&gt;

&lt;p&gt;Behind the scenes, PM2 manages four Node.js instances for load balancing. The &lt;code&gt;/api/system-health&lt;/code&gt; endpoint checks five items (globe_js, uploads_dir, env_forge_url, env_forge_key, database); the earlier eight-point description existed only in v1.4.1 changelog notes before the implementation was simplified.&lt;/p&gt;

&lt;h4&gt;
  
  
  The API Architecture
&lt;/h4&gt;

&lt;p&gt;The backend is built around a single unified tRPC API composed of 24 domain routers (posts, admin, payments, search, AI, etc.). The admin dashboard primarily interacts with the dedicated admin router, ensuring separation of concerns and easier maintenance.&lt;/p&gt;

&lt;h4&gt;
  
  
  Version 1.4.4: Streamlining Post Creation
&lt;/h4&gt;

&lt;p&gt;The latest update (v1.4.4, shipped 2026-05-20) streamlined the post-creation process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Country Selector Removed&lt;/strong&gt;: The country is now auto-detected from the subdomain (e.g., &lt;code&gt;pl.24ad.info&lt;/code&gt; → Poland).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Currency Auto-Selection&lt;/strong&gt;: The currency is auto-selected from the subdomain on form load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Category Filters&lt;/strong&gt;: Labels and options are now translated using values from the database’s translations field.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While basic currency auto-detection existed since v1.1-1.2, this update made it fully subdomain-driven.&lt;/p&gt;

&lt;h4&gt;
  
  
  Lessons Learned
&lt;/h4&gt;

&lt;p&gt;Building 24ad.info has been a journey filled with technical challenges and valuable lessons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Test Payment Flows Thoroughly&lt;/strong&gt;: Small oversights can lead to significant issues when money is involved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI is a Double-Edged Sword&lt;/strong&gt;: While AI automates many tasks, it requires careful tuning and monitoring to ensure accuracy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compression Matters&lt;/strong&gt;: Converting all images to WebP improved performance without requiring complex configurations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture is Key&lt;/strong&gt;: A well-structured API architecture (like tRPC’s domain routers) simplifies maintenance and scalability.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  What’s Next?
&lt;/h4&gt;

&lt;p&gt;We’re constantly iterating on 24ad.info, with plans to improve the AI models, expand language support, and enhance the user experience. If you’re interested in following our journey, check out the live site at &lt;a href="https://24ad.info" rel="noopener noreferrer"&gt;https://24ad.info&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Building in public isn’t always glamorous — bugs happen, features fail — but the transparency and community feedback make it all worthwhile. Here’s to the next chapter in the 24ad.info story.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Why Your Database Needs an Autopilot (And How to Install One in 60 Seconds)</title>
      <dc:creator>slawekluzny</dc:creator>
      <pubDate>Sat, 15 Aug 2026 23:22:35 +0000</pubDate>
      <link>https://dev.to/slawekluzny/why-your-database-needs-an-autopilot-and-how-to-install-one-in-60-seconds-42en</link>
      <guid>https://dev.to/slawekluzny/why-your-database-needs-an-autopilot-and-how-to-install-one-in-60-seconds-42en</guid>
      <description>&lt;h1&gt;
  
  
  Why Your Database Needs an Autopilot (And How to Install One)
&lt;/h1&gt;

&lt;p&gt;Sentinel AI was born from the frustration of manually firefighting problems that could be prevented. Here's how it works under the hood.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Silent Killers of Database Performance
&lt;/h2&gt;

&lt;p&gt;Every production server can suffer from performance issues. Traditional monitoring tools alert you &lt;em&gt;after&lt;/em&gt; these problems cause pain. Sentinel takes a different approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Autopilot Works
&lt;/h2&gt;

&lt;p&gt;Sentinel can automatically detect issues, analyze affected queries, create missing indexes, and log the action with before/after plans. All without waking anyone up. Here's the technical breakdown:&lt;/p&gt;

&lt;h3&gt;
  
  
  The Analysis Engine
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Slow Query Mining&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Parses slow query logs and uses real EXPLAIN plans.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pathology Detection&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Flags relevant query patterns and resource issues.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Safety Checks&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Before any automatic index creation, relevant system conditions are verified.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Security Layer
&lt;/h3&gt;

&lt;p&gt;While optimizing databases, Sentinel also handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automated IP blocking&lt;/strong&gt; via Fail2Ban and CrowdSec integration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attack correlation&lt;/strong&gt; across server fleets (Enterprise tier)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSL expiry monitoring&lt;/strong&gt; with proper TLS handshake checks&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;Installation typically takes 2-4 minutes (venv setup + pip dependencies). For Linux-based operating systems:&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;-sSL&lt;/span&gt; https://sentinel-ai.info/install.sh | &lt;span class="nb"&gt;sudo &lt;/span&gt;bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This installs Sentinel and starts the web UI.&lt;/p&gt;

&lt;p&gt;The Pro tier starts from $49/month for up to 5 servers and unlocks automated index creation and cross-server security sync. Enterprise costs $149/month for up to 10 servers and adds the AI Chat interface and Cross-Fleet IP Reputation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Expect After Deployment
&lt;/h2&gt;

&lt;p&gt;Performance metrics are established and slow queries are analyzed. Automatic optimizations are applied and security patterns are identified. Optimization history becomes visible in the dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Unfair Advantage
&lt;/h2&gt;

&lt;p&gt;Servers running Sentinel see recurring issues addressed proactively.&lt;/p&gt;

&lt;p&gt;Try the free Basic tier for 1 production server on one production server - no credit card required. It might just change how you think about server management.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How 24ad.info's AI Classifieds Actually Works Under the Hood</title>
      <dc:creator>slawekluzny</dc:creator>
      <pubDate>Sat, 15 Aug 2026 23:19:45 +0000</pubDate>
      <link>https://dev.to/slawekluzny/how-24adinfos-ai-classifieds-actually-works-under-the-hood-41nc</link>
      <guid>https://dev.to/slawekluzny/how-24adinfos-ai-classifieds-actually-works-under-the-hood-41nc</guid>
      <description>&lt;h1&gt;
  
  
  How 24ad.info's AI Classifieds Actually Works Under the Hood
&lt;/h1&gt;

&lt;p&gt;This morning I woke up to a support ticket that read: &lt;em&gt;"Your AI suggested I sell my sofa for £3.50 – is this a bug or are you running a charity?"&lt;/em&gt; Fair question. Let's peel back the layers on how 24ad.info's AI-powered classifieds actually functions when you upload that photo of your old iPhone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Vision Pipeline: From Pixels to Price
&lt;/h2&gt;

&lt;p&gt;When you drag-and-drop an image onto our post creation form, here's what happens before the form auto-fills:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Image Analysis&lt;/strong&gt;: We pipe your image through OpenRouter's Gemini 2.5 Flash (not the 2.0 version mentioned in some docs – that's text-only). This gives us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Object identification ("iPhone 12, 64GB, good condition")&lt;/li&gt;
&lt;li&gt;Context clues (a phone on a desk suggests "for sale", while one in a repair shop suggests "services offered")&lt;/li&gt;
&lt;li&gt;Visible wear/defects (scratches, cracked screen)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Market Positioning&lt;/strong&gt;: The AI cross-references the identified item with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recent similar listings in your country&lt;/li&gt;
&lt;li&gt;Age-based depreciation curves&lt;/li&gt;
&lt;li&gt;Local demand signals (search volume for "iPhone 12" in your region)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Price Suggestion&lt;/strong&gt;: This is where the £3.50 sofa incident occurred. The system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Takes the median price of comparable items&lt;/li&gt;
&lt;li&gt;Applies a nudge discount to encourage faster sales&lt;/li&gt;
&lt;li&gt;Caps absurd outliers (hence why you'll never see £0.01 suggestions)
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simplified price suggestion logic (from server/routers/ai.ts)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;suggestPrice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DetectedItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;country&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;=&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;comparables&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;findSimilarListings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;country&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;medianPrice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateMedian&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;comparables&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;applyPsychologicalPricing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;medianPrice&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Encourages pricing slightly under market&lt;/span&gt;
    &lt;span class="nx"&gt;country&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 key lesson? Always review the AI's work – it's optimized for quick listing, not perfect pricing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Payment Bug That Cost Us Refunds
&lt;/h2&gt;

&lt;p&gt;In our Stripe integration, we hit a textbook "assumption failure":&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Flow&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User pays for a "Premium" listing package&lt;/li&gt;
&lt;li&gt;Stripe Checkout completes&lt;/li&gt;
&lt;li&gt;Our &lt;code&gt;verifyCheckoutSession&lt;/code&gt; marks payment as complete... and stopped there&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Missing Step&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;   &lt;span class="c1"&gt;// Pre-fix logic (simplified)&lt;/span&gt;
   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handlePaymentSuccess&lt;/span&gt; &lt;span class="o"&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;sessionId&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="nf"&gt;markPaymentCompleted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
     &lt;span class="c1"&gt;// applyPackageToPost() WASN'T CALLED HERE&lt;/span&gt;
   &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Fallout&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Users paid for features they didn't receive&lt;/li&gt;
&lt;li&gt;Our support inbox exploded with "Where's my premium badge?"&lt;/li&gt;
&lt;li&gt;Manual refunds + package activations took developer time to resolve&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fix shipped in v1.4.4 was embarrassingly simple – a single function call added to the verification flow. Now we have end-to-end tests mocking this exact scenario.&lt;/p&gt;

&lt;h2&gt;
  
  
  Location Search: One SQL Query to Rule Them All
&lt;/h2&gt;

&lt;p&gt;Unlike platforms that layer full-text search with post-filtering, our location handling is brutally simple:&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;// Location search (from server/db-search.ts)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&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;getPostsNearbyAdvanced&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt; &lt;span class="c1"&gt;// bounding-box + Haversine in JS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key decisions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No Fuzzy Location&lt;/strong&gt;: You're either searching in "London" or within a radius of a point – no magic "near me" expansion&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single-Pass Query&lt;/strong&gt;: Avoids the common pitfall of:

&lt;ul&gt;
&lt;li&gt;First find matching text&lt;/li&gt;
&lt;li&gt;Then filter by location&lt;/li&gt;
&lt;li&gt;Then re-sort by relevance&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Postcode Exactitude&lt;/strong&gt;: UK postcodes (e.g., "SW1A 1AA") resolve to precision via an exact-match lookup against an indexed varchar(20) postcode column on the cities table&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Deployment Script That Saved Our Translations
&lt;/h2&gt;

&lt;p&gt;Early on, we nuked all German/French translations during a routine deploy. The culprit?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rsync &lt;span class="nt"&gt;--delete&lt;/span&gt; ./dist /var/www/24ad.info/  &lt;span class="c"&gt;# DELETED locales/ folder&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, &lt;code&gt;deploy_prod.sh&lt;/code&gt; enforces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rsync &lt;span class="nt"&gt;--exclude&lt;/span&gt; &lt;span class="s2"&gt;"locales"&lt;/span&gt; ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we have a pre-deploy checklist:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Verify &lt;code&gt;enabled-languages.json&lt;/code&gt; exists on server&lt;/li&gt;
&lt;li&gt;Confirm no &lt;code&gt;--delete&lt;/code&gt; flag without &lt;code&gt;--exclude&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Test on dev.24ad.info first&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why tRPC's Router Composition Matters
&lt;/h2&gt;

&lt;p&gt;Our API structure avoids the "mega-router" anti-pattern:&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;// How we compose routers (server/routers/index.ts)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appRouter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createTRPCRouter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;adminRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;// Dashboard ops&lt;/span&gt;
  &lt;span class="na"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;postsRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;// CRUD operations&lt;/span&gt;
  &lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;aiRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;// Vision/translation&lt;/span&gt;
  &lt;span class="na"&gt;payments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;paymentsRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Stripe flows&lt;/span&gt;
  &lt;span class="na"&gt;search&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;searchRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// Location-aware&lt;/span&gt;
  &lt;span class="c1"&gt;// ...19 more&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Client-side usage remains clean:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;trpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getById&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clear boundaries (admin routes never mix with public ones)&lt;/li&gt;
&lt;li&gt;Separate middleware stacks&lt;/li&gt;
&lt;li&gt;Easier code splitting if needed later&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;The current roadmap includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI-generated listing quality scores&lt;/li&gt;
&lt;li&gt;Automated repricing suggestions for stale posts&lt;/li&gt;
&lt;li&gt;Bulk image analysis for multi-photo uploads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But for now, the system works – even if it occasionally thinks your designer sofa belongs at a car boot sale. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;[Edit: The £3.50 sofa was actually a cushion. The user cropped the image poorly. Lesson learned – we now detect item scale better.]&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>product</category>
      <category>software</category>
    </item>
    <item>
      <title>How We Fixed the Silent Payment Bug That Cost Us Real Money</title>
      <dc:creator>slawekluzny</dc:creator>
      <pubDate>Sun, 19 Jul 2026 22:47:33 +0000</pubDate>
      <link>https://dev.to/slawekluzny/how-we-fixed-the-silent-payment-bug-that-cost-us-real-money-257b</link>
      <guid>https://dev.to/slawekluzny/how-we-fixed-the-silent-payment-bug-that-cost-us-real-money-257b</guid>
      <description>&lt;h1&gt;
  
  
  How We Fixed the Silent Payment Bug That Cost Us Real Money
&lt;/h1&gt;

&lt;p&gt;This morning I was reviewing Stripe logs when I noticed something odd—successfully paid "Premium Listing" purchases where the posts never actually got premium status. Our users were paying, but the promised visibility boost wasn't happening. Oops.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bug That Lived in Production for Approximately 13-19 Days
&lt;/h2&gt;

&lt;p&gt;The issue was straightforward, yet nasty:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User pays for a premium listing package via Stripe Checkout
&lt;/li&gt;
&lt;li&gt;Client-side completes the payment flow
&lt;/li&gt;
&lt;li&gt;Backend marks payment as completed in our database
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Critical step missing&lt;/strong&gt;: The actual package benefits (featuredUntil/highlightedUntil timestamps) never got applied to the post
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We discovered this when a user emailed saying "I paid £4.99 but my post looks the same."&lt;/p&gt;

&lt;h2&gt;
  
  
  The Anatomy of the Failure
&lt;/h2&gt;

&lt;p&gt;Our payment flow had two parallel paths:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Admin Bypass Path (Working)&lt;/strong&gt;&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;// Working admin flow&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;markPaymentCompleted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paymentId&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;applyPackageToPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;packageType&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ✅&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Stripe Checkout Path (Broken)&lt;/strong&gt;&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;// Broken production flow until v1.4.4&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;markPaymentCompleted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paymentId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ❌ Stopped here&lt;/span&gt;
&lt;span class="c1"&gt;// applyPackageToPost() was never called&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The missing line was a classic copy-paste omission—we'd abstracted the package application logic but forgot to call it in the Stripe verification path.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix (v1.4.4)
&lt;/h2&gt;

&lt;p&gt;We patched it with surgical precision:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Added &lt;code&gt;applyPackageToPost()&lt;/code&gt; to the checkout verification flow
&lt;/li&gt;
&lt;li&gt;Made the admin bypass use the same unified function
&lt;/li&gt;
&lt;li&gt;Added transaction wrapping to ensure atomic updates
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Fixed in v1.4.4&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;verified&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;verifyCheckoutSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sessionId&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;verified&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&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;tx&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="nf"&gt;markPaymentCompleted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paymentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tx&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;applyPackageToPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;packageType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ✅ Now called&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;Key details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses Drizzle ORM transactions
&lt;/li&gt;
&lt;li&gt;Same function handles both Stripe and admin flows
&lt;/li&gt;
&lt;li&gt;Logs package application separately for auditing
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why This Hurt More Than Typical Bugs
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Silent failure&lt;/strong&gt; - No errors in logs, payments showed as "completed"
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delayed detection&lt;/strong&gt; - Users assumed premium features were subtle
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reputation risk&lt;/strong&gt; - Paying customers weren't getting what they paid for
&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Payment flows need dual verification&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
We now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Log package application separately from payment completion
&lt;/li&gt;
&lt;li&gt;Have a nightly job checking for paid-but-unapplied packages
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Transactional boundaries matter&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The fix wrapped everything in a DB transaction—no more partial updates.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Admin tools should use the same paths as users&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The admin bypass worked because it called the full flow. The Stripe path didn't.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Monitor what you monetize&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
We added Stripe webhook logging and dashboard monitoring for:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Payments completed vs packages applied (tracked via checkout session IDs)
&lt;/li&gt;
&lt;li&gt;Time delta between payment and feature activation (logged in checkout verification flow)
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Stack That Caught It
&lt;/h2&gt;

&lt;p&gt;Our monitoring eventually flagged the issue through user reports and internal checks.&lt;/p&gt;

&lt;p&gt;The Stripe webhook handler is a stub. Package activation happens in the verify-checkout-session flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current Architecture (Post-Fix)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;graph TD
    A[Stripe Checkout] --&amp;gt;|Session ID| B(verifyCheckoutSession)
    B --&amp;gt; C{Valid?}
    C --&amp;gt;|Yes| D[markPaymentCompleted]
    D --&amp;gt; E[applyPackageToPost]
    E --&amp;gt; F[Update post.featuredUntil]
    C --&amp;gt;|No| G[Log failed verification]
    H[Admin Bypass] --&amp;gt; E
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key improvements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Single code path for both flows
&lt;/li&gt;
&lt;li&gt;Transactional integrity
&lt;/li&gt;
&lt;li&gt;Audit logging at each step
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  If We Had to Do It Again
&lt;/h2&gt;

&lt;p&gt;We'd:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Write the failing test first&lt;/strong&gt; - No test covered the post-payment state
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor payment-to-feature latency&lt;/strong&gt; - Would've caught it within hours
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add feature toggle checks&lt;/strong&gt; - Verify premium posts actually appear differently
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fix shipped in v1.4.4. Since then, zero reported issues—and our premium purchase conversion rate increased. Turns out people buy more when the features actually work. Who knew?&lt;/p&gt;

</description>
      <category>backend</category>
      <category>debugging</category>
      <category>fintech</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>The Memory Problem Nobody Talks About in AI Coding Assistants</title>
      <dc:creator>slawekluzny</dc:creator>
      <pubDate>Thu, 09 Jul 2026 20:19:35 +0000</pubDate>
      <link>https://dev.to/slawekluzny/the-memory-problem-nobody-talks-about-in-ai-coding-assistants-1n8l</link>
      <guid>https://dev.to/slawekluzny/the-memory-problem-nobody-talks-about-in-ai-coding-assistants-1n8l</guid>
      <description>&lt;h1&gt;
  
  
  The Memory Problem Nobody Talks About in AI Coding Assistants
&lt;/h1&gt;

&lt;p&gt;This morning, I watched Claude Code confidently propose a FastAPI solution for a Node.js project. Again. It's not Claude's fault - it's working exactly as designed, with the memory span of a goldfish between sessions. Here's what's really happening under the hood.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Context Amnesia Problem
&lt;/h2&gt;

&lt;p&gt;Every new session with an AI coding assistant follows the same painful pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You paste your architecture diagram (again)&lt;/li&gt;
&lt;li&gt;You explain your stack choices (again)
&lt;/li&gt;
&lt;li&gt;You recap yesterday's progress (again)&lt;/li&gt;
&lt;li&gt;You re-explain why that caching approach failed (again)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The brutal truth? These tools have no persistent memory. That "session memory" feature? It's just a transcript summary that degrades over time. By the next session, your carefully explained architecture becomes "the system uses some backend components."&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Isn't Just a Token Problem
&lt;/h2&gt;

&lt;p&gt;Most discussions focus on context window sizes, but that misses the real issue:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-project contamination&lt;/strong&gt;: Your FastAPI project's details leak into your Node.js work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version drift&lt;/strong&gt;: Yesterday's "working solution" becomes today's "maybe we should try..."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decision amnesia&lt;/strong&gt;: Rejected approaches resurface like bad pennies&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How Project Memory Actually Works (When It Works)
&lt;/h2&gt;

&lt;p&gt;The working solution isn't revolutionary - it's boringly simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A structured knowledge map&lt;/strong&gt;: Not a blob of text, but a navigable hierarchy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Status tracking&lt;/strong&gt;: ✓ verified vs ✗ failed vs ⚠ in-progress&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decision logging&lt;/strong&gt;: Why we chose X and rejected Y&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delta loading&lt;/strong&gt;: Only what changed since last time&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The magic happens in the mundane details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;.project-brain/
  index.md          # The map
  projects/
    api/
      caching.md    # ✓ verified
      auth.md       # ⚠ WIP
  _decisions.md     # &amp;gt; Chose FastAPI over Flask because...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What This Fixes That Session Memory Can't
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cross-project isolation&lt;/strong&gt;: No more Node.js suggestions in your Python repo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version awareness&lt;/strong&gt;: The AI knows when something was superseded&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trust signaling&lt;/strong&gt;: Human-verified vs AI-inferred markers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Staleness detection&lt;/strong&gt;: Review topics past their review date before using&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The validator catches subtle but critical errors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Conflicting tech claims (Redis vs Memcached in same project)&lt;/li&gt;
&lt;li&gt;Stale credentials&lt;/li&gt;
&lt;li&gt;Misfiled project details&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Real Workflow Difference
&lt;/h2&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# User: "Add rate limiting"
# AI: "Let's use Redis!" 
# (despite Postgres being the documented choice)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# AI: "Project specifies Postgres - using pg_cron 
# as implemented in caching.md (✓ verified)"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the difference between an assistant that fights your system and one that works with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation Truths
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Zero runtime dependencies&lt;/strong&gt;: Just Python 3's stdlib&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plain Markdown&lt;/strong&gt;: No proprietary formats&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Git-friendly&lt;/strong&gt;: The entire brain is diffable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool-agnostic&lt;/strong&gt;: Works with any file-reading agent&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The install is deliberately boring:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/OoneBreath/claude-code-project-brain.git
&lt;span class="nb"&gt;cd &lt;/span&gt;claude-code-project-brain
./install.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why This Isn't Just Another Note-Taking System
&lt;/h2&gt;

&lt;p&gt;The key differentiators:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Agent-first design&lt;/strong&gt;: Structured for programmatic consumption&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Status primitives&lt;/strong&gt;: Not just notes - verified decisions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conflict detection&lt;/strong&gt;: Hard tech incompatibility checks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tiered loading&lt;/strong&gt;: Recent projects HOT, older ones COLD&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's the difference between a notebook and an aircraft maintenance log.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Unsexy Truth About AI Assistants
&lt;/h2&gt;

&lt;p&gt;The biggest productivity gains don't come from fancier models - they come from fixing the mundane plumbing issues like persistent context. Project Brain isn't glamorous, but it solves the actual problem developers face daily: stopping the endless re-explanation cycle and finally getting back to coding.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>coding</category>
      <category>llm</category>
    </item>
    <item>
      <title>277,000 Attacks Blocked — Here's What the Data Shows</title>
      <dc:creator>slawekluzny</dc:creator>
      <pubDate>Fri, 19 Jun 2026 10:12:14 +0000</pubDate>
      <link>https://dev.to/slawekluzny/277000-attacks-blocked-heres-what-the-data-shows-38pe</link>
      <guid>https://dev.to/slawekluzny/277000-attacks-blocked-heres-what-the-data-shows-38pe</guid>
      <description>&lt;h1&gt;
  
  
  303,970 Attacks Blocked — Here's What the Data Shows
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Category:&lt;/strong&gt; Threat Intelligence&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Tags:&lt;/strong&gt; threat intelligence, fail2ban, attack data, VPS security  &lt;/p&gt;




&lt;p&gt;This morning, Sentinel's audit dashboard ticked over to 303,970 total blocked attacks (single server lifetime counter).  &lt;/p&gt;

&lt;p&gt;That's not a crisis. That's Tuesday.  &lt;/p&gt;

&lt;p&gt;If your Linux VPS is exposed to the internet, this is baseline background radiation. The question isn't whether you're being attacked — you are. The question is whether you can see it happening.  &lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;303,970&lt;/strong&gt; total attacks blocked
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5&lt;/strong&gt; primary origin countries
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;99.2%&lt;/strong&gt; block success rate
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;7,002&lt;/strong&gt; peak single day
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~450&lt;/strong&gt; average daily per server
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't alarm-bell numbers. They're normal. Here's what they actually mean.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Where Attacks Come From
&lt;/h2&gt;

&lt;p&gt;The geographic breakdown was predictable but revealing:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;China (42%)&lt;/strong&gt; – Bulk SSH brute-force attempts, mostly automated scripts hammering port 22.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Russia (31%)&lt;/strong&gt; – Similar pattern, but with more variation in target ports (MySQL, Redis, and HTTP admin panels).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;United States (12%)&lt;/strong&gt; – Almost entirely commercial VPN exit nodes and hosting providers. Attackers renting "clean" infrastructure to bypass IP reputation filters.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Germany (9%)&lt;/strong&gt; – Same as US, plus a surprising number of compromised IoT devices.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Netherlands (6%)&lt;/strong&gt; – Overwhelmingly Tor exit nodes and rented datacenter IPs.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key insight? &lt;strong&gt;Shared blocklists are reactive, not proactive.&lt;/strong&gt; By the time an IP shows up in CrowdSec or Fail2Ban community lists, attackers have already cycled to fresh infrastructure.  &lt;/p&gt;

&lt;h2&gt;
  
  
  The Daily Attack Pattern
&lt;/h2&gt;

&lt;p&gt;Sentinel's hourly logs show two clear attack windows:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;02:00–05:00 UTC&lt;/strong&gt; – Peak automated scanning. When your sysadmin is asleep.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;14:00–17:00 UTC&lt;/strong&gt; – Correlates with Southeast Asian business hours (likely cron jobs).
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your SSH keys aren’t locked down or your Fail2Ban settings are too lenient, the problem surfaces at 3am — not during your working hours.  &lt;/p&gt;

&lt;h2&gt;
  
  
  The 0.8% That Got Through
&lt;/h2&gt;

&lt;p&gt;Of the ~2,200 attempts that bypassed perimeter defenses:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;78%&lt;/strong&gt; hit application-layer rate limits (e.g., WordPress login throttling)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;21%&lt;/strong&gt; reached login forms but failed authentication
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0.1%&lt;/strong&gt; (3 incidents) triggered high-severity alerts — all caught by Sentinel’s real-time monitoring
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The audit trail showed exactly why these slipped through:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Overly generous maxretry settings&lt;/strong&gt; (e.g., allowing 10 attempts before banning)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IP rotation within a subnet&lt;/strong&gt; (avoiding single-IP thresholds until we added CIDR blocks)
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What This Means for Your Server
&lt;/h2&gt;

&lt;p&gt;Blocking 303,970 attacks is table stakes. The real value is &lt;strong&gt;visibility&lt;/strong&gt;:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Know &lt;strong&gt;which&lt;/strong&gt; 303,970
&lt;/li&gt;
&lt;li&gt;Know &lt;strong&gt;from where&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Know &lt;strong&gt;when and how&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without this data, you’re either:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Flying blind (assuming "no alerts = no attacks"), or
&lt;/li&gt;
&lt;li&gt;Overreacting (e.g., banning entire countries unnecessarily)
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Sentinel’s Audit dashboard surfaces this in real time. You’ll see:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Emerging attack patterns before they escalate
&lt;/li&gt;
&lt;li&gt;Whether your defenses are properly tuned
&lt;/li&gt;
&lt;li&gt;A clean audit trail for clients or compliance
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How Sentinel Handles It
&lt;/h2&gt;

&lt;p&gt;Here’s what happens under the hood when an attack hits a Sentinel-monitored server:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Real-time detection&lt;/strong&gt; – Fail2Ban + CrowdSec + custom rules.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic subnet blocking&lt;/strong&gt; – If an IP from &lt;code&gt;/24&lt;/code&gt; is malicious, the whole range gets banned.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-fleet sync&lt;/strong&gt; – Blocked IPs are shared with other Sentinel servers via Queen.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI analysis&lt;/strong&gt; – Claude AI reviews logs to suggest tighter rules.
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Basic tier:&lt;/strong&gt; Free for 1 server (&lt;a href="https://sentinel-ai.info" rel="noopener noreferrer"&gt;install in 60 seconds&lt;/a&gt;)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pro tier:&lt;/strong&gt; $49/month for 5 servers (adds automated DB optimizations)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise:&lt;/strong&gt; $149/month for 10 servers (AI Chat, fleet-wide threat intel)
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn’t about fearmongering. It’s about &lt;strong&gt;acknowledging reality&lt;/strong&gt; — and having the tools to handle it.  &lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;Data source: Sentinel Audit dashboard, live production servers&lt;/li&gt;
&lt;li&gt;Last updated: June 2026
&lt;/li&gt;
&lt;li&gt;servers monitored: 3 production servers&lt;/li&gt;
&lt;li&gt;OS breakdown: 100% Ubuntu 24.04 across all 3 servers (no Debian/RHEL)
&lt;/li&gt;
&lt;li&gt;Full dataset available to Enterprise users via Queen API&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Why I Built a Self-Healing Server Agent (And Why It's Not Magic)</title>
      <dc:creator>slawekluzny</dc:creator>
      <pubDate>Fri, 19 Jun 2026 10:07:28 +0000</pubDate>
      <link>https://dev.to/slawekluzny/why-i-built-a-self-healing-server-agent-and-why-its-not-magic-oe4</link>
      <guid>https://dev.to/slawekluzny/why-i-built-a-self-healing-server-agent-and-why-its-not-magic-oe4</guid>
      <description>&lt;h1&gt;
  
  
  Why I Built a Self-Healing Server Agent (And Why It's Not Magic)
&lt;/h1&gt;

&lt;p&gt;Sentinel can detect and contain suspicious activity while surfacing database performance issues without requiring human intervention.&lt;/p&gt;

&lt;p&gt;This is what happens when you stop treating servers like pets and start treating them like cattle with PhDs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem That Started It All
&lt;/h2&gt;

&lt;p&gt;The post-mortem on recurring production issues revealed two uncomfortable truths:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring tools lie by omission&lt;/strong&gt; - They'll happily show you green status lights while your database quietly suffocates from unoptimized queries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alert fatigue is real&lt;/strong&gt; - When everything is "urgent", nothing is&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Traditional monitoring gives you dashboards. What you actually need is a system that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understands normal behavior for YOUR stack&lt;/li&gt;
&lt;li&gt;Can distinguish between "interesting" and "critical"&lt;/li&gt;
&lt;li&gt;Takes safe actions without human hand-holding&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How Sentinel Actually Works (Without the Hype)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Monitoring Layer That Doesn't Sleep
&lt;/h3&gt;

&lt;p&gt;Sentinel's monitoring isn't revolutionary—it's just thorough where others aren't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Process watching&lt;/strong&gt; via direct systemd integration (not just PID checks)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSL monitoring&lt;/strong&gt; that actually performs TLS handshakes (not just date checks)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database analysis&lt;/strong&gt; that reads slow logs AND EXPLAIN plans&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attack detection&lt;/strong&gt; correlating Fail2Ban, CrowdSec and UFW&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When it detects failed SSH attempts from an IP, it blocks that IP at the firewall level.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Database Autopilot That Earns Its Keep
&lt;/h3&gt;

&lt;p&gt;Here's what our MySQL optimization pipeline looks like today:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Slow query analysis&lt;/strong&gt; - Identifies queries taking above a configurable threshold&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EXPLAIN plan review&lt;/strong&gt; - Flags full table scans, temp tables, bad joins&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Index suggestion&lt;/strong&gt; - Proposes specific multi-column indexes with expected impact&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safe application&lt;/strong&gt; - Automatically creates indexes marked high confidence&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Security Model That Fights Back
&lt;/h3&gt;

&lt;p&gt;Most security tools are museum curators—they catalog attacks after they happen. Sentinel operates more like a bouncer with photographic memory:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IP reputation sharing&lt;/strong&gt; - Banned IPs sync to our central "Queen" server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Behavioral blocking&lt;/strong&gt; - Detects probing patterns before full attacks launch&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic containment&lt;/strong&gt; - Isolates compromised services without killing entire boxes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Architecture Choices That Made It Possible
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why Python + Flask Instead of Go
&lt;/h3&gt;

&lt;p&gt;We get asked this constantly. For a monitoring tool, Go seems obvious—but we chose Python because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SQLAlchemy&lt;/strong&gt; gives us ORM flexibility across SQLite (state) and monitored DBs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;APScheduler&lt;/strong&gt; handles background jobs without k8s complexity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prototyping speed&lt;/strong&gt; mattered more than raw performance initially&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Queen-Sentinel Protocol
&lt;/h3&gt;

&lt;p&gt;Every Sentinel node phones home to Queen with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Aggregated metrics (never raw logs)&lt;/li&gt;
&lt;li&gt;New security signatures&lt;/li&gt;
&lt;li&gt;Anonymized query patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Queen responds with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Updated threat intelligence&lt;/li&gt;
&lt;li&gt;Cross-fleet performance recommendations&lt;/li&gt;
&lt;li&gt;Safe action approvals (for Enterprise tier)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This keeps bandwidth minimal while enabling fleet-wide learning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing That Doesn't Require an MBA to Understand
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Servers&lt;/th&gt;
&lt;th&gt;Price/Mo&lt;/th&gt;
&lt;th&gt;Key Features&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Monitoring, security alerts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;$49&lt;/td&gt;
&lt;td&gt;DB autopilot, email alerts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enterprise&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;$149&lt;/td&gt;
&lt;td&gt;AI chat, cross-server IP blocking&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The economics are simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pro pays for itself if it prevents engineer time&lt;/li&gt;
&lt;li&gt;Enterprise makes sense when managing multiple production workloads&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;Enter your email at sentinel-ai.info to receive the Queen-vault installer. This:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creates /opt/sentinel with isolated Python env&lt;/li&gt;
&lt;li&gt;Sets up systemd services&lt;/li&gt;
&lt;li&gt;Installs Fail2Ban filters (if missing)&lt;/li&gt;
&lt;li&gt;Surfaces the admin-UI access URL&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No Docker. No k8s manifests. Just a binary watchdog that works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Road Ahead
&lt;/h2&gt;

&lt;p&gt;PostgreSQL query analysis is now generally available. The philosophy remains unchanged: your servers should defend, heal and optimize themselves. Because at 3 AM, you'd rather be sleeping than debugging PHP-FPM.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://sentinel-ai.info" rel="noopener noreferrer"&gt;Try Sentinel free&lt;/a&gt; on one server—no credit card required. The worst that can happen is you'll stop getting paged for problems that fix themselves.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>devops</category>
      <category>monitoring</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
