<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>Afrizzal — Blog</title><description>Notes on IT leadership, CRM &amp; revenue systems, advertising, and AI automation — by Afrizzal.</description><link>https://blog.afrizzal.pro/</link><item><title>A validation error that was actually a rendering race</title><link>https://blog.afrizzal.pro/blog/a-validation-error-that-was-actually-a-rendering-race/</link><guid isPermaLink="true">https://blog.afrizzal.pro/blog/a-validation-error-that-was-actually-a-rendering-race/</guid><description>AIDA&apos;s Settings AI Features form blocked Save with a &quot;Select a model&quot; error right after switching providers. The real defect sat two React commits away, inside a hidden native select Radix uses to bridge into native form submission — and it never raised an error of its own.</description><pubDate>Fri, 17 Jul 2026 00:00:00 GMT</pubDate><category>Systems &amp; Performance</category><category>reliability</category><category>testing</category><category>architecture</category><category>typescript</category></item><item><title>The save that refuses to fail with its side effect</title><link>https://blog.afrizzal.pro/blog/the-save-that-refuses-to-fail-with-its-side-effect/</link><guid isPermaLink="true">https://blog.afrizzal.pro/blog/the-save-that-refuses-to-fail-with-its-side-effect/</guid><description>A Google Reviews listing-confirm route taught me that &apos;it saved&apos; and &apos;everything it triggered afterward also worked&apos; are two different claims, and a response body should never merge them into one.</description><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><category>Systems &amp; Performance</category><category>reliability</category><category>architecture</category><category>nextjs</category></item><item><title>A 404 that lies on purpose is more honest than a 403</title><link>https://blog.afrizzal.pro/blog/a-404-that-lies-on-purpose/</link><guid isPermaLink="true">https://blog.afrizzal.pro/blog/a-404-that-lies-on-purpose/</guid><description>Fixing a cross-tenant IDOR taught me that &apos;access denied&apos; and &apos;does not exist&apos; are the same message when the requester should never learn which one is true.</description><pubDate>Wed, 15 Jul 2026 00:00:00 GMT</pubDate><category>Systems &amp; Performance</category><category>security</category><category>reliability</category><category>architecture</category><category>data-integrity</category></item><item><title>One writer, one lock: a race-proof FIFO stock ledger on plain PostgreSQL</title><link>https://blog.afrizzal.pro/blog/race-proof-fifo-stock-ledger-postgres/</link><guid isPermaLink="true">https://blog.afrizzal.pro/blog/race-proof-fifo-stock-ledger-postgres/</guid><description>Two POS terminals sell the last unit in the same second. Inventory survives that only if every stock mutation goes through one function, one advisory lock, and one idempotency key — and if sub-cent rounding residue dies the moment stock hits zero.</description><pubDate>Thu, 09 Jul 2026 00:00:00 GMT</pubDate><category>Systems &amp; Performance</category><category>postgres</category><category>concurrency</category><category>data-integrity</category><category>architecture</category></item><item><title>The support ticket that tried to close its own prompt</title><link>https://blog.afrizzal.pro/blog/support-ticket-tried-to-close-its-own-prompt/</link><guid isPermaLink="true">https://blog.afrizzal.pro/blog/support-ticket-tried-to-close-its-own-prompt/</guid><description>AIDA classifies support tickets with an LLM, so the classifier has to treat every ticket as a potential attack. The fence around the ticket text is real, but it isn&apos;t the guarantee — the guarantee is that a successful attack still can&apos;t do anything.</description><pubDate>Wed, 08 Jul 2026 00:00:00 GMT</pubDate><category>AI &amp; Automation</category><category>ai</category><category>llm</category><category>security</category><category>reliability</category></item><item><title>The retry that would have referenced itself</title><link>https://blog.afrizzal.pro/blog/retry-that-would-have-referenced-itself/</link><guid isPermaLink="true">https://blog.afrizzal.pro/blog/retry-that-would-have-referenced-itself/</guid><description>AIDA&apos;s outbound email job derives References and In-Reply-To by querying every prior email-bearing message on the ticket. On a retry, the message being sent already has its own Message-ID persisted from the failed first attempt — without an explicit exclusion, it would show up in its own References chain.</description><pubDate>Tue, 07 Jul 2026 00:00:00 GMT</pubDate><category>Systems &amp; Performance</category><category>reliability</category><category>queues</category><category>email</category><category>architecture</category></item><item><title>An HTTP 202 is a promise: why I built OmniSync</title><link>https://blog.afrizzal.pro/blog/omnisync-http-202-is-a-promise/</link><guid isPermaLink="true">https://blog.afrizzal.pro/blog/omnisync-http-202-is-a-promise/</guid><description>Every webhook integration works in the demo. Production loses events in the silence after you reply 200 — so I built OmniSync around one guarantee: once an event is acknowledged, it is never silently lost. At-least-once delivery, idempotent everything.</description><pubDate>Mon, 06 Jul 2026 00:00:00 GMT</pubDate><category>Systems &amp; Performance</category><category>architecture</category><category>reliability</category><category>queues</category><category>postgres</category></item><item><title>The upload limit that mattered wasn&apos;t the one in my code</title><link>https://blog.afrizzal.pro/blog/upload-limit-that-wasnt-in-my-code/</link><guid isPermaLink="true">https://blog.afrizzal.pro/blog/upload-limit-that-wasnt-in-my-code/</guid><description>AIDA&apos;s public intake route checks content length, per-file size, a running total, and byte-sniffed MIME type before saving an attachment. None of that mattered once Next.js&apos;s own proxy layer silently truncated the request body first, with no error back to the client.</description><pubDate>Mon, 06 Jul 2026 00:00:00 GMT</pubDate><category>Systems &amp; Performance</category><category>nextjs</category><category>reliability</category><category>architecture</category></item><item><title>My E2E suite caught a wrong AI diagnosis — then it caught mine too</title><link>https://blog.afrizzal.pro/blog/e2e-suite-caught-wrong-ai-diagnosis/</link><guid isPermaLink="true">https://blog.afrizzal.pro/blog/e2e-suite-caught-wrong-ai-diagnosis/</guid><description>An AI agent blamed a failing Playwright test on Next.js response caching. I traced the code and blamed middleware. We were both wrong about the observation: the test&apos;s &quot;anonymous&quot; context was quietly authenticated. Two stacked bugs, two confident stories, and the re-run as the only reviewer that counted.</description><pubDate>Sun, 05 Jul 2026 00:00:00 GMT</pubDate><category>AI &amp; Automation</category><category>ai</category><category>testing</category><category>nextjs</category><category>reliability</category></item><item><title>Hello, World</title><link>https://blog.afrizzal.pro/blog/hello-world/</link><guid isPermaLink="true">https://blog.afrizzal.pro/blog/hello-world/</guid><description>Why I started this blog, what I plan to write about, and a quick tour of the formatting it supports.</description><pubDate>Thu, 18 Jun 2026 00:00:00 GMT</pubDate><category>Notes</category><category>meta</category><category>career</category></item><item><title>What one box can actually hold (and why you never measured it)</title><link>https://blog.afrizzal.pro/blog/lean-systems-modest-hardware/</link><guid isPermaLink="true">https://blog.afrizzal.pro/blog/lean-systems-modest-hardware/</guid><description>Your scale ceiling is an arithmetic result, not a feeling — throughput is concurrency over latency, and a CPU-bound box tops out at cores divided by CPU-seconds per request. Most systems fall over at a small fraction of that number because of coordination and copies, not because the machine is small.</description><pubDate>Mon, 15 Jun 2026 00:00:00 GMT</pubDate><category>Systems &amp; Performance</category><category>architecture</category><category>performance</category><category>scaling</category></item><item><title>CRM plumbing that moves revenue</title><link>https://blog.afrizzal.pro/blog/crm-plumbing-revenue/</link><guid isPermaLink="true">https://blog.afrizzal.pro/blog/crm-plumbing-revenue/</guid><description>A CRM is only as good as the data flowing into it. The interesting work is rarely the UI — it is the plumbing.</description><pubDate>Fri, 12 Jun 2026 00:00:00 GMT</pubDate><category>CRM &amp; Revenue</category><category>crm</category><category>revenue</category></item><item><title>Use the model only where it beats a regex on your own data</title><link>https://blog.afrizzal.pro/blog/practical-ai-automation/</link><guid isPermaLink="true">https://blog.afrizzal.pro/blog/practical-ai-automation/</guid><description>A model call is the slowest, priciest, least deterministic dependency in your whole system, so you ration it like an unreliable third-party API you call as rarely as you can. The practical skill is putting the model only where fuzzy understanding is genuinely required and doing the boring 80% with deterministic code you can test.</description><pubDate>Tue, 09 Jun 2026 00:00:00 GMT</pubDate><category>AI &amp; Automation</category><category>ai</category><category>automation</category><category>architecture</category></item><item><title>Read the slow query log first</title><link>https://blog.afrizzal.pro/blog/reading-slow-query-logs/</link><guid isPermaLink="true">https://blog.afrizzal.pro/blog/reading-slow-query-logs/</guid><description>Before reaching for a bigger server, read the slow query log. It is the cheapest performance win you will ever get.</description><pubDate>Fri, 05 Jun 2026 00:00:00 GMT</pubDate><category>Systems &amp; Performance</category><category>database</category><category>performance</category></item><item><title>A more-correct rebuild that nobody trusts is worth nothing</title><link>https://blog.afrizzal.pro/blog/spreadsheets-to-dashboards/</link><guid isPermaLink="true">https://blog.afrizzal.pro/blog/spreadsheets-to-dashboards/</guid><description>A spreadsheet quietly running part of the business is a production system with real users and a proven spec. You earn the right to replace it by matching its numbers exactly — bugs included — before you change a single thing.</description><pubDate>Mon, 01 Jun 2026 00:00:00 GMT</pubDate><category>CRM &amp; Revenue</category><category>analytics</category><category>data-modeling</category><category>crm</category></item></channel></rss>