<?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: MATT ROSE</title>
    <description>The latest articles on DEV Community by MATT ROSE (@matt_rose_9d0fe88d3533a4f).</description>
    <link>https://dev.to/matt_rose_9d0fe88d3533a4f</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%2F3982058%2Faff47fa6-d182-44bc-a834-d906eaa84bc0.png</url>
      <title>DEV Community: MATT ROSE</title>
      <link>https://dev.to/matt_rose_9d0fe88d3533a4f</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/matt_rose_9d0fe88d3533a4f"/>
    <language>en</language>
    <item>
      <title>The Recovery Industry Has a Decisioning Problem Every recovery team I've talked to has the same complaint in a different outfit.</title>
      <dc:creator>MATT ROSE</dc:creator>
      <pubDate>Tue, 07 Jul 2026 22:16:54 +0000</pubDate>
      <link>https://dev.to/matt_rose_9d0fe88d3533a4f/the-recovery-industry-has-a-decisioning-problem-every-recovery-team-ive-talked-to-has-the-same-58h4</link>
      <guid>https://dev.to/matt_rose_9d0fe88d3533a4f/the-recovery-industry-has-a-decisioning-problem-every-recovery-team-ive-talked-to-has-the-same-58h4</guid>
      <description>&lt;p&gt;"Our skip data is good but our contact rate is terrible." "We're calling the right number but it's the wrong person." "We can't tell which accounts are worth working until we've already worked them."&lt;/p&gt;

&lt;p&gt;These aren't data problems. The data is often fine. The problem is that nobody turns the data into a decision before treatment begins.&lt;/p&gt;

&lt;p&gt;What Actually Happens Today&lt;br&gt;
An account lands in a queue. Someone appended a phone. Someone else checked an address. A third system returned a name match. The collector picks it up and dials.&lt;/p&gt;

&lt;p&gt;Nobody asked: Is this definitively the right person? Is contact legally permitted right now? Is there an attorney on record? Is the debtor deceased and an estate path required instead?&lt;/p&gt;

&lt;p&gt;Those questions get answered during the call — or they don't get answered at all. That's where wrong-party contact happens. That's where suppression violations happen. That's where compliance risk accumulates invisibly until it isn't invisible anymore.&lt;/p&gt;

&lt;p&gt;A Decision Is Not a Score&lt;br&gt;
The industry defaulted to scores because scores are easy to produce. A number comes back, you set a threshold, accounts above it go to the dialer.&lt;/p&gt;

&lt;p&gt;But a score doesn't tell you why. It doesn't tell you the contact is blocked because there's an attorney of record. It doesn't tell you the phone is unverified. It doesn't tell you the account subject is deceased and the path forward is through a probate representative.&lt;/p&gt;

&lt;p&gt;A score compresses the reason out of the answer. That compression is exactly where the compliance risk lives.&lt;/p&gt;

&lt;p&gt;Gallus Resolve returns a decision — not a score. Every account gets a documented routing outcome:&lt;/p&gt;

&lt;p&gt;Clear to work — identity and contact path confirmed, policy cleared, proceed&lt;br&gt;
Suppress — hard block; do not contact&lt;br&gt;
Enrich — identity exists but contact path needs additional verification&lt;br&gt;
Manual review — meaningful evidence with an ambiguity a human must resolve&lt;br&gt;
Insufficient confidence — file is too thin to route safely&lt;br&gt;
Each outcome comes with the reason, the contact paths that are open, the contact paths that are blocked, and the specific next allowed step.&lt;/p&gt;

&lt;p&gt;The Next Allowed Step&lt;br&gt;
This is the part most decisioning systems skip entirely.&lt;/p&gt;

&lt;p&gt;Knowing an account is not clear to work is useful. Knowing why and what to do next is what actually moves the file forward.&lt;/p&gt;

&lt;p&gt;For a deceased debtor, the next allowed step is not "skip trace harder." It is "identify the estate representative and route through probate policy."&lt;/p&gt;

&lt;p&gt;For an account with attorney representation detected, the next allowed step is not "try a different phone." It is "route to the attorney contact script and cease direct debtor contact."&lt;/p&gt;

&lt;p&gt;For an account where identity is confirmed but the direct phone is unverified, the next allowed step is "verify ownership through a second independent source before dialing."&lt;/p&gt;

&lt;p&gt;Gallus Resolve specifies the next allowed step for every account, every time.&lt;/p&gt;

&lt;p&gt;Who This Is For&lt;br&gt;
Healthcare RCM — stale patient accounts, estate and deceased routing, compliance-aware signal handling across fragmented placement histories.&lt;/p&gt;

&lt;p&gt;Collection agencies — wrong-party risk reduction, attorney routing, spouse and place-of-employment path evaluation before treatment.&lt;/p&gt;

&lt;p&gt;Probate recovery — estate representative identification, executor detection, probate court routing.&lt;/p&gt;

&lt;p&gt;Collection law firms — recovery capacity indicators, litigation-aware suppression, legal workflow routing.&lt;/p&gt;

&lt;p&gt;Controlled Trial&lt;br&gt;
Gallus Resolve is accepting controlled trial requests. Trials run on a file of up to 500 accounts, return documented routing decisions for each account, and operate in audit-only mode — no consumer contact during the trial.&lt;/p&gt;

&lt;p&gt;Account data is accepted only through a secure intake path — never via email.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://gallusresolve.com/trial/gallus-resolve" rel="noopener noreferrer"&gt;https://gallusresolve.com/trial/gallus-resolve&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;→ View a sample decision packet: &lt;a href="https://gallusresolve.com/trial/gallus-resolve/sample" rel="noopener noreferrer"&gt;https://gallusresolve.com/trial/gallus-resolve/sample&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Gallus Resolve is a decision-support and audit system. It does not provide legal advice. Clients remain responsible for their own contact policies, permissible-use determinations, and compliance review.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>When the Output Is Judgment: Designing Decision Layers for Fragmented Identity Signals</title>
      <dc:creator>MATT ROSE</dc:creator>
      <pubDate>Mon, 06 Jul 2026 00:13:02 +0000</pubDate>
      <link>https://dev.to/matt_rose_9d0fe88d3533a4f/when-the-output-is-judgment-designing-decision-layers-for-fragmented-identity-signals-l30</link>
      <guid>https://dev.to/matt_rose_9d0fe88d3533a4f/when-the-output-is-judgment-designing-decision-layers-for-fragmented-identity-signals-l30</guid>
      <description>&lt;p&gt;ORIGINALLY PUBLISHED:  &lt;/p&gt;

&lt;p&gt;&lt;a href="http://www.gallusresolve.com/articles/when-the-output-is-judgment/index.html" rel="noopener noreferrer"&gt;www.gallusresolve.com/articles/when-the-output-is-judgment/index.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GALLUS Resolve is built around a simple architectural belief: high-volume identity work does not need more raw records. It needs controlled decisions.&lt;/p&gt;

&lt;p&gt;A file may contain thousands of people, incomplete fields, stale attributes, conflicting signals, and operational risk hidden inside rows that look ordinary. The buyer does not need another pile of possible answers. The buyer needs to know which records can move forward, which records should stop, which records require review, and why each record was routed that way.&lt;/p&gt;

&lt;p&gt;That distinction changes the architecture.&lt;/p&gt;

&lt;p&gt;A raw-data system optimizes for retrieval. A decision-layer system optimizes for controlled commitment.&lt;/p&gt;

&lt;p&gt;Retrieval asks, “What can we find?”&lt;/p&gt;

&lt;p&gt;Decisioning asks, “What are we willing to stand behind?”&lt;/p&gt;

&lt;p&gt;Gallus Resolve is designed for the second question.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision is the product
&lt;/h2&gt;

&lt;p&gt;In many systems, the output is treated as data: a phone number, an email, an address, a score, a profile, a match, or a list of candidates.&lt;/p&gt;

&lt;p&gt;In Gallus Resolve, the output is an operator decision.&lt;/p&gt;

&lt;p&gt;That may sound like a wording choice, but architecturally it changes the whole system. A record is not finished because something was found. A record is finished when it can be placed into an operational state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Safe-contact eligible&lt;/li&gt;
&lt;li&gt;Manual review&lt;/li&gt;
&lt;li&gt;No match&lt;/li&gt;
&lt;li&gt;Do not contact&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those outcomes are intentionally plain. They are designed for operations, not curiosity. The operator should not need to inspect a hundred raw fields to understand what the system is recommending.&lt;/p&gt;

&lt;p&gt;The internal system can be complex. The external contract must stay simple.&lt;/p&gt;

&lt;p&gt;That is one of the core design principles: complexity may exist inside the decision layer, but the output must reduce complexity for the user.&lt;/p&gt;

&lt;h2&gt;
  
  
  A file should collapse into queues
&lt;/h2&gt;

&lt;p&gt;High-volume review breaks down when every row receives equal attention.&lt;/p&gt;

&lt;p&gt;If a team uploads 10,000 records, the operator should not be asked to read 10,000 stories.&lt;/p&gt;

&lt;p&gt;The right interface is a compression layer.&lt;/p&gt;

&lt;p&gt;A batch should become something like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;6,840 ready outcomes&lt;/li&gt;
&lt;li&gt;1,920 no match&lt;/li&gt;
&lt;li&gt;940 blocked&lt;/li&gt;
&lt;li&gt;260 manual review&lt;/li&gt;
&lt;li&gt;40 urgent exceptions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The operator starts with the 40.&lt;/p&gt;

&lt;p&gt;That is the important shift. Gallus is not designed to make every record interesting. It is designed to make most records boring, then isolate the cases that deserve human judgment.&lt;/p&gt;

&lt;p&gt;The architecture should make this visible. The dashboard should not be a row viewer first. It should be a command center:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What happened to the file?&lt;/li&gt;
&lt;li&gt;How many records resolved cleanly?&lt;/li&gt;
&lt;li&gt;How many were blocked?&lt;/li&gt;
&lt;li&gt;How many need review?&lt;/li&gt;
&lt;li&gt;Which exception queues matter first?&lt;/li&gt;
&lt;li&gt;What is the next operator action?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The batch view is not a convenience feature. It is the product surface that makes the system usable at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ambiguity is a result, not a failure
&lt;/h2&gt;

&lt;p&gt;A good decision system must treat ambiguity as a valid output.&lt;/p&gt;

&lt;p&gt;In many products, unresolved records feel like failures. They get buried, retried endlessly, or pushed forward with a weak warning because the product wants to appear useful.&lt;/p&gt;

&lt;p&gt;That is dangerous in a domain where the cost of acting on a bad record can be higher than the cost of pausing.&lt;/p&gt;

&lt;p&gt;Gallus Resolve treats ambiguity as something to route, not something to hide.&lt;/p&gt;

&lt;p&gt;A record that cannot be safely advanced should become a review item, a blocked item, or a no-match item. Each of those outcomes is useful because it prevents the operator from confusing “something was found” with “something is usable.”&lt;/p&gt;

&lt;p&gt;This is a major architectural boundary. The system must never be pressured into making the UI look better by making the decision less honest.&lt;/p&gt;

&lt;p&gt;A conservative decision is still a decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  The operator should handle exceptions, not volume
&lt;/h2&gt;

&lt;p&gt;Human judgment is valuable. Human attention is limited.&lt;/p&gt;

&lt;p&gt;The mistake many systems make is using human review as a dumping ground. If the machine is uncertain, it throws the whole record to a person. Over time, the review queue becomes a landfill: too many items, too little prioritization, no sense of urgency, and no clear explanation of why anything is there.&lt;/p&gt;

&lt;p&gt;Gallus Resolve needs the opposite pattern.&lt;/p&gt;

&lt;p&gt;Manual review should be structured. Exceptions should be categorized. The operator should know what kind of judgment is needed before opening the case.&lt;/p&gt;

&lt;p&gt;The practical categories can be simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ready for approved next step&lt;/li&gt;
&lt;li&gt;Needs human review&lt;/li&gt;
&lt;li&gt;Blocked from action&lt;/li&gt;
&lt;li&gt;Unresolved&lt;/li&gt;
&lt;li&gt;Insufficient support&lt;/li&gt;
&lt;li&gt;Requires client decision&lt;/li&gt;
&lt;li&gt;No usable outcome&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The names matter less than the principle: every exception must have a reason to exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Gallus Resolve is designed around a different unit of value: not the record, not the signal, not the possible match, but the decision.&lt;/p&gt;

&lt;p&gt;That requires a specific architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Treat each record as an operational decision candidate.&lt;/li&gt;
&lt;li&gt;Collapse large files into outcome distributions.&lt;/li&gt;
&lt;li&gt;Route ambiguity into named exception queues.&lt;/li&gt;
&lt;li&gt;Reserve human attention for cases that require judgment.&lt;/li&gt;
&lt;li&gt;Produce review summaries without exposing proprietary mechanics.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is a system that does not ask operators to keep up with thousands of records. &amp;nbsp;Instead, it gives them the few decisions that matter.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>softwareengineering</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Every Boolean Permission Is a Race Condition</title>
      <dc:creator>MATT ROSE</dc:creator>
      <pubDate>Tue, 23 Jun 2026 17:26:54 +0000</pubDate>
      <link>https://dev.to/matt_rose_9d0fe88d3533a4f/every-boolean-permission-is-a-race-condition-2pnn</link>
      <guid>https://dev.to/matt_rose_9d0fe88d3533a4f/every-boolean-permission-is-a-race-condition-2pnn</guid>
      <description>&lt;p&gt;Most production systems do not fail because they forgot to check permission.&lt;/p&gt;

&lt;p&gt;They fail because the permission was true too early.&lt;/p&gt;

&lt;p&gt;That is the bug.&lt;/p&gt;

&lt;p&gt;The system checked whether an action was allowed. The answer was yes. Then time passed. State changed. The account changed. The provider changed. The resource changed. The policy changed. The user clicked again. Another worker acted first. A webhook arrived late. A payment expired. A record was cancelled. A session went stale.&lt;/p&gt;

&lt;p&gt;Then the system performed the action anyway because it was still holding an old boolean.&lt;/p&gt;

&lt;p&gt;canProceed: true&lt;/p&gt;

&lt;p&gt;That is not authorization.&lt;/p&gt;

&lt;p&gt;That is a stale opinion.&lt;br&gt;
Booleans Are Not Decisions&lt;/p&gt;

&lt;p&gt;A boolean tells you almost nothing.&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "allowed": true&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Allowed by whom?&lt;/p&gt;

&lt;p&gt;When?&lt;/p&gt;

&lt;p&gt;Against what source?&lt;/p&gt;

&lt;p&gt;For which action?&lt;/p&gt;

&lt;p&gt;For which resource?&lt;/p&gt;

&lt;p&gt;For how long?&lt;/p&gt;

&lt;p&gt;Under which conditions?&lt;/p&gt;

&lt;p&gt;Was the answer live or cached?&lt;/p&gt;

&lt;p&gt;Was the provider available?&lt;/p&gt;

&lt;p&gt;Was this a real decision or a fallback?&lt;/p&gt;

&lt;p&gt;Can the decision expire?&lt;/p&gt;

&lt;p&gt;Can it be revoked?&lt;/p&gt;

&lt;p&gt;A boolean cannot answer any of that.&lt;/p&gt;

&lt;p&gt;A boolean is a compression artifact. It takes a messy, time-bound, conditional decision and crushes it into true or false.&lt;/p&gt;

&lt;p&gt;Then the rest of the system pretends that nothing important was lost.&lt;/p&gt;

&lt;p&gt;Something important was lost.&lt;/p&gt;

&lt;p&gt;Time.&lt;br&gt;
The UI Is Not the Authority&lt;/p&gt;

&lt;p&gt;A green button is not permission.&lt;/p&gt;

&lt;p&gt;A checked box is not permission.&lt;/p&gt;

&lt;p&gt;A dashboard badge is not permission.&lt;/p&gt;

&lt;p&gt;A frontend state is not permission.&lt;/p&gt;

&lt;p&gt;A user opened a page at 10:01. The backend said the action was available. The frontend rendered the button. The user clicked it at 10:04.&lt;/p&gt;

&lt;p&gt;What happened between 10:01 and 10:04?&lt;/p&gt;

&lt;p&gt;Maybe nothing.&lt;/p&gt;

&lt;p&gt;Maybe everything.&lt;/p&gt;

&lt;p&gt;The account could have been suspended. The payment could have failed. The resource could have been reassigned. The policy window could have closed. The external provider could have revoked the state. Another process could have already consumed the opportunity.&lt;/p&gt;

&lt;p&gt;The UI does not know.&lt;/p&gt;

&lt;p&gt;The UI is a photograph of an old decision.&lt;/p&gt;

&lt;p&gt;Production systems should treat every frontend permission as stale by default.&lt;br&gt;
This Is TOCTOU for Product Logic&lt;/p&gt;

&lt;p&gt;Security people know this bug.&lt;/p&gt;

&lt;p&gt;Time of check. Time of use.&lt;/p&gt;

&lt;p&gt;You check a file, then use it later. Between the check and the use, the file changes.&lt;/p&gt;

&lt;p&gt;The same bug exists in ordinary business logic.&lt;/p&gt;

&lt;p&gt;Except instead of files, the objects are:&lt;/p&gt;

&lt;p&gt;accounts&lt;br&gt;
orders&lt;br&gt;
bookings&lt;br&gt;
payments&lt;br&gt;
subscriptions&lt;br&gt;
appointments&lt;br&gt;
permissions&lt;br&gt;
devices&lt;br&gt;
contracts&lt;br&gt;
inventory&lt;br&gt;
external provider state&lt;/p&gt;

&lt;p&gt;The pattern is the same:&lt;/p&gt;

&lt;p&gt;check state&lt;br&gt;
state changes&lt;br&gt;
use stale check&lt;/p&gt;

&lt;p&gt;This is not an edge case. This is the default condition of distributed software.&lt;/p&gt;

&lt;p&gt;The world changes between reads and writes.&lt;/p&gt;

&lt;p&gt;If your system cannot represent that, it will eventually act on fiction.&lt;br&gt;
Permission Should Be a Lease&lt;/p&gt;

&lt;p&gt;Stop treating permission as a boolean.&lt;/p&gt;

&lt;p&gt;Treat it as a lease.&lt;/p&gt;

&lt;p&gt;A permission lease says:&lt;/p&gt;

&lt;p&gt;This subject may perform this action&lt;br&gt;
against this resource&lt;br&gt;
under these conditions&lt;br&gt;
from this authority&lt;br&gt;
until this time&lt;br&gt;
unless revoked earlier.&lt;/p&gt;

&lt;p&gt;That is authorization.&lt;/p&gt;

&lt;p&gt;This is not:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "allowed": true&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;A real permission object should look more like this:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "decision": "allowed",&lt;br&gt;
  "decision_id": "dec_123",&lt;br&gt;
  "subject_id": "user_456",&lt;br&gt;
  "resource_id": "res_789",&lt;br&gt;
  "action": "execute_transfer",&lt;br&gt;
  "authority": "billing_service",&lt;br&gt;
  "source": "live",&lt;br&gt;
  "issued_at": "2026-06-22T18:45:00Z",&lt;br&gt;
  "expires_at": "2026-06-22T18:46:00Z",&lt;br&gt;
  "conditions": [&lt;br&gt;
    "account_active",&lt;br&gt;
    "payment_method_valid",&lt;br&gt;
    "policy_window_open"&lt;br&gt;
  ]&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Now the system has something it can reason about.&lt;/p&gt;

&lt;p&gt;Not just “yes.”&lt;/p&gt;

&lt;p&gt;A scoped, time-bound claim.&lt;br&gt;
The Action Boundary Must Recheck&lt;/p&gt;

&lt;p&gt;The place where the action happens is the only place that matters.&lt;/p&gt;

&lt;p&gt;Not the page load.&lt;/p&gt;

&lt;p&gt;Not the eligibility screen.&lt;/p&gt;

&lt;p&gt;Not the preview step.&lt;/p&gt;

&lt;p&gt;Not the prior API response.&lt;/p&gt;

&lt;p&gt;The action boundary.&lt;/p&gt;

&lt;p&gt;That is where permission must be validated.&lt;/p&gt;

&lt;p&gt;Bad:&lt;/p&gt;

&lt;p&gt;async function executeAction(input) {&lt;br&gt;
  if (!input.allowed) {&lt;br&gt;
    throw new Error("Not allowed");&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;return performAction(input);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This is not secure. It is theater.&lt;/p&gt;

&lt;p&gt;Better:&lt;/p&gt;

&lt;p&gt;async function executeAction(input) {&lt;br&gt;
  const lease = await getPermissionLease(input.decision_id);&lt;/p&gt;

&lt;p&gt;if (!lease) {&lt;br&gt;
    return {&lt;br&gt;
      success: false,&lt;br&gt;
      status: "permission_missing"&lt;br&gt;
    };&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;if (new Date(lease.expires_at) &amp;lt; new Date()) {&lt;br&gt;
    return {&lt;br&gt;
      success: false,&lt;br&gt;
      status: "permission_expired"&lt;br&gt;
    };&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;if (lease.subject_id !== input.subject_id) {&lt;br&gt;
    return {&lt;br&gt;
      success: false,&lt;br&gt;
      status: "subject_mismatch"&lt;br&gt;
    };&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;if (lease.resource_id !== input.resource_id) {&lt;br&gt;
    return {&lt;br&gt;
      success: false,&lt;br&gt;
      status: "resource_mismatch"&lt;br&gt;
    };&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;if (lease.action !== input.action) {&lt;br&gt;
    return {&lt;br&gt;
      success: false,&lt;br&gt;
      status: "action_mismatch"&lt;br&gt;
    };&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;return performAction(input);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;For irreversible actions, even that is not enough.&lt;/p&gt;

&lt;p&gt;You recheck the authority live.&lt;/p&gt;

&lt;p&gt;async function executeIrreversibleAction(input) {&lt;br&gt;
  const lease = await validatePermissionLease(input);&lt;/p&gt;

&lt;p&gt;if (!lease.valid) {&lt;br&gt;
    return lease.refusal;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;const live = await fetchAuthoritativeState(input.resource_id);&lt;/p&gt;

&lt;p&gt;if (!live.allowed) {&lt;br&gt;
    return {&lt;br&gt;
      success: false,&lt;br&gt;
      status: "state_changed",&lt;br&gt;
      reason: live.reason&lt;br&gt;
    };&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;return performAction(input);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;The more serious the action, the closer the check must be to the use.&lt;br&gt;
Cached Permission Needs a Warning Label&lt;/p&gt;

&lt;p&gt;Caching is not the enemy.&lt;/p&gt;

&lt;p&gt;Unlabeled caching is.&lt;/p&gt;

&lt;p&gt;Some workflows can safely use cached state. Many cannot. The problem is when the system hides the difference.&lt;/p&gt;

&lt;p&gt;This is not enough:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "allowed": true&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This is useful:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "decision": "allowed",&lt;br&gt;
  "source": "cached",&lt;br&gt;
  "cached_at": "2026-06-22T18:40:00Z",&lt;br&gt;
  "max_age_ms": 30000,&lt;br&gt;
  "authority": "account_service"&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Now downstream code can refuse cached authority when live authority is required.&lt;/p&gt;

&lt;p&gt;if (permission.source === "cached" &amp;amp;&amp;amp; action.requires_live_authority) {&lt;br&gt;
  return {&lt;br&gt;
    success: false,&lt;br&gt;
    status: "live_authority_required"&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;That is the difference between an intentional fallback and an accidental lie.&lt;br&gt;
“Success” Should Not Hide the State That Made It Possible&lt;/p&gt;

&lt;p&gt;A lot of systems store the final action but not the decision that allowed it.&lt;/p&gt;

&lt;p&gt;That is a weak audit trail.&lt;/p&gt;

&lt;p&gt;After something goes wrong, the question is not only:&lt;/p&gt;

&lt;p&gt;What happened?&lt;/p&gt;

&lt;p&gt;The question is:&lt;/p&gt;

&lt;p&gt;What did the system believe when it acted?&lt;/p&gt;

&lt;p&gt;You need the decision snapshot.&lt;/p&gt;

&lt;p&gt;create table action_decision_ledger (&lt;br&gt;
  id uuid primary key default gen_random_uuid(),&lt;/p&gt;

&lt;p&gt;subject_id text not null,&lt;br&gt;
  resource_id text not null,&lt;br&gt;
  action text not null,&lt;/p&gt;

&lt;p&gt;decision text not null,&lt;br&gt;
  authority text not null,&lt;br&gt;
  decision_source text not null,&lt;/p&gt;

&lt;p&gt;issued_at timestamptz not null,&lt;br&gt;
  expires_at timestamptz,&lt;br&gt;
  used_at timestamptz,&lt;/p&gt;

&lt;p&gt;conditions jsonb,&lt;br&gt;
  final_status text,&lt;br&gt;
  refusal_reason text,&lt;/p&gt;

&lt;p&gt;created_at timestamptz default now()&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;Logs are not enough.&lt;/p&gt;

&lt;p&gt;Logs tell you what the code said.&lt;/p&gt;

&lt;p&gt;A decision ledger tells you what the system believed.&lt;/p&gt;

&lt;p&gt;That is what matters.&lt;br&gt;
Refusal States Are Product Features&lt;/p&gt;

&lt;p&gt;A serious system knows how to say no.&lt;/p&gt;

&lt;p&gt;Not just with a generic error.&lt;/p&gt;

&lt;p&gt;With a precise refusal state.&lt;/p&gt;

&lt;p&gt;permission_expired&lt;br&gt;
live_authority_required&lt;br&gt;
state_changed&lt;br&gt;
authority_unavailable&lt;br&gt;
authority_rejected&lt;br&gt;
policy_window_closed&lt;br&gt;
duplicate_action_in_progress&lt;br&gt;
manual_review_required&lt;br&gt;
provider_unconfigured&lt;br&gt;
scope_mismatch&lt;/p&gt;

&lt;p&gt;These are not error messages.&lt;/p&gt;

&lt;p&gt;They are control flow.&lt;/p&gt;

&lt;p&gt;Each one tells the system what to do next.&lt;/p&gt;

&lt;p&gt;Retry.&lt;/p&gt;

&lt;p&gt;Refresh.&lt;/p&gt;

&lt;p&gt;Recheck.&lt;/p&gt;

&lt;p&gt;Escalate.&lt;/p&gt;

&lt;p&gt;Route to review.&lt;/p&gt;

&lt;p&gt;Ask for new authorization.&lt;/p&gt;

&lt;p&gt;Stop completely.&lt;/p&gt;

&lt;p&gt;Generic failure is useless. Named refusal is operational intelligence.&lt;br&gt;
The Pattern&lt;/p&gt;

&lt;p&gt;The pattern is simple.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ask the authoritative system for permission.&lt;/li&gt;
&lt;li&gt;Issue a short-lived decision lease.&lt;/li&gt;
&lt;li&gt;Bind the lease to subject, resource, and action.&lt;/li&gt;
&lt;li&gt;Revalidate the lease at execution.&lt;/li&gt;
&lt;li&gt;Recheck live authority for irreversible actions.&lt;/li&gt;
&lt;li&gt;Refuse with named states when stale.&lt;/li&gt;
&lt;li&gt;Store the decision snapshot.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is not complicated.&lt;/p&gt;

&lt;p&gt;It is just stricter than most systems are willing to be.&lt;/p&gt;

&lt;p&gt;Most systems prefer the comfort of true.&lt;/p&gt;

&lt;p&gt;Production does not care about comfort.&lt;/p&gt;

&lt;p&gt;Production cares whether the thing is still true when you act.&lt;br&gt;
The Engineering Law&lt;/p&gt;

&lt;p&gt;Here is the law:&lt;/p&gt;

&lt;p&gt;Never treat permission as a boolean.&lt;br&gt;
Treat permission as a time-bound claim.&lt;/p&gt;

&lt;p&gt;A boolean says:&lt;/p&gt;

&lt;p&gt;yes&lt;/p&gt;

&lt;p&gt;A permission lease says:&lt;/p&gt;

&lt;p&gt;yes, for this actor, against this resource, for this action, from this authority, under these conditions, until this moment&lt;/p&gt;

&lt;p&gt;That is the difference between a UI state and a production decision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Thought&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The most dangerous bugs are not always wrong at the moment they are created.&lt;/p&gt;

&lt;p&gt;Sometimes they begin as truth.&lt;/p&gt;

&lt;p&gt;The user really was eligible.&lt;/p&gt;

&lt;p&gt;The resource really was available.&lt;/p&gt;

&lt;p&gt;The policy really did allow it.&lt;/p&gt;

&lt;p&gt;The provider really did agree.&lt;/p&gt;

&lt;p&gt;The button really did belong on the page.&lt;/p&gt;

&lt;p&gt;Then the world changed.&lt;/p&gt;

&lt;p&gt;And the system acted as if it had not.&lt;/p&gt;

&lt;p&gt;That is why every boolean permission is a race condition.&lt;/p&gt;

&lt;p&gt;The fix is not another checkmark.&lt;/p&gt;

&lt;p&gt;The fix is time.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>distributedsystems</category>
      <category>security</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>This post is my submission for DEV Education Track: Build Apps with Google AI Studio.</title>
      <dc:creator>MATT ROSE</dc:creator>
      <pubDate>Mon, 22 Jun 2026 03:21:22 +0000</pubDate>
      <link>https://dev.to/matt_rose_9d0fe88d3533a4f/this-post-is-my-submission-for-dev-education-track-build-apps-with-google-ai-studio-49la</link>
      <guid>https://dev.to/matt_rose_9d0fe88d3533a4f/this-post-is-my-submission-for-dev-education-track-build-apps-with-google-ai-studio-49la</guid>
      <description>&lt;p&gt;&lt;em&gt;This post is my submission for &lt;a href="https://dev.to/deved/build-apps-with-google-ai-studio"&gt;DEV Education Track: Build Apps with Google AI Studio&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What I Built&lt;/p&gt;

&lt;p&gt;I used Google AI Studio to build TeleBurn, an ultra-private utility application designed for secure messaging and disposable video communication. The core premise is simple: talk, and then burn it—allowing users to communicate without exposing their phone numbers.&lt;/p&gt;

&lt;p&gt;The project was executed in two phases:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The Core Prototype: Built directly inside Google AI Studio to lock down the system instructions, prompt logic, and ephemeral state management.

The Production Deployment (teleburn.site): A hardened, production-ready implementation wrapped in a disciplined, gothic-modern interface that draws on minimal luxury aesthetics, stripping away standard SaaS visual noise to focus purely on utility.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To get the architecture and UI dialed in, I used prompts focused on strict design constraints and solid logic:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Generate a React layout for an ephemeral messaging interface. The aesthetic must be gothic-modern and strictly adhere to minimal luxury design principles. Use stark, high-contrast typography, absolute grid discipline reminiscent of 1990s-era luxury brands, and avoid any generic SaaS icons or bloated UI elements."

"Write the React state management and backend logic for the disposable message payload. Ensure the data is securely isolated and completely wiped from memory upon the 'destroy' trigger. The implementation must leave zero residual traces in the local cache once read, treating the session with absolute security."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Demo&lt;/p&gt;

&lt;p&gt;You can explore the project across both stages of its development:  &lt;a href="https://ai.studio/apps/f905eb16-4ed0-4863-ae35-28a51bc66ecc" rel="noopener noreferrer"&gt;https://ai.studio/apps/f905eb16-4ed0-4863-ae35-28a51bc66ecc&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;and &lt;/p&gt;

&lt;p&gt;&lt;a href="https://teleburn.site" rel="noopener noreferrer"&gt;https://teleburn.site&lt;/a&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The AI Studio Prototype: Explore the core engine and run the interactive applet directly via the Google AI Studio App Link.  https://ai.studio/apps/f905eb16-4ed0-4863-ae35-28a51bc66ecc

The Live Production Site: See the finalized, high-fidelity implementation at https://teleburn.site.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvy0fvkeom2wwf9wzslmu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvy0fvkeom2wwf9wzslmu.png" alt=" " width="800" height="666"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbwu8gxfse88z47u9n1aq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbwu8gxfse88z47u9n1aq.png" alt=" " width="800" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjq4cin98xmnm7fga7dcs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjq4cin98xmnm7fga7dcs.png" alt=" " width="800" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My Experience&lt;/p&gt;

&lt;p&gt;Building this highlighted how effectively Google AI Studio bridges the gap between raw system architecture and UI execution. The most surprising part was how well the model adhered to strict design directives—getting it to output a modern, highly disciplined interface required less hand-holding than expected. Instead of fighting the generated code, I was able to spend my time refining the secure data destruction mechanics and deployment.&lt;/p&gt;

</description>
      <category>deved</category>
      <category>learngoogleaistudio</category>
      <category>ai</category>
      <category>gemini</category>
    </item>
    <item>
      <title>When No Answer Beats a Wrong Answer: Designing Precision-First Systems</title>
      <dc:creator>MATT ROSE</dc:creator>
      <pubDate>Wed, 17 Jun 2026 17:35:53 +0000</pubDate>
      <link>https://dev.to/matt_rose_9d0fe88d3533a4f/when-no-answer-beats-a-wrong-answer-designing-precision-first-systems-3hh5</link>
      <guid>https://dev.to/matt_rose_9d0fe88d3533a4f/when-no-answer-beats-a-wrong-answer-designing-precision-first-systems-3hh5</guid>
      <description>&lt;p&gt;Most systems optimize for getting an answer. Some have to optimize for never getting the wrong one. Here's how building for asymmetric error costs changes everything about your architecture.&lt;/p&gt;

&lt;p&gt;A note before we start: this is an architecture essay, not a product tear-down. It's just a design philosophy that I think more engineers should be deliberate about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two kinds of "wrong"
&lt;/h2&gt;

&lt;p&gt;Most of the systems we build are quietly optimized around a comfortable assumption: that a missed answer and a wrong answer cost about the same. A search result you didn't surface and a search result that's slightly off are both just "not great." You tune for accuracy, you ship, you move on.&lt;/p&gt;

&lt;p&gt;Then occasionally you build something where that assumption is not just wrong — it's dangerous.&lt;/p&gt;

&lt;p&gt;I've spent the last couple of years building a system where the cost of the two errors is wildly asymmetric. Failing to produce an answer is mildly disappointing. Producing the &lt;strong&gt;wrong&lt;/strong&gt; answer is unrecoverable — it doesn't just degrade the experience, it damages trust in a way you can't apologize your way out of.&lt;/p&gt;

&lt;p&gt;Once you internalize that asymmetry, almost every default in modern system design starts to look subtly miscalibrated. This is a tour of what changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accuracy is the wrong headline metric
&lt;/h2&gt;

&lt;p&gt;The first thing that has to go is "accuracy."&lt;/p&gt;

&lt;p&gt;Accuracy blends two very different failures into one number. A model that's 95% accurate might be making its 5% of mistakes by staying quiet — or by confidently asserting falsehoods. Those are not the same system. One is cautious; the other is a liability.&lt;/p&gt;

&lt;p&gt;The metrics that actually matter when errors are asymmetric are &lt;strong&gt;precision&lt;/strong&gt; and the shape of your failure distribution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Precision&lt;/strong&gt;: of the answers you &lt;em&gt;did&lt;/em&gt; commit to, how many were right?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Abstention rate&lt;/strong&gt;: how often did you correctly decline to answer?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;False commit rate&lt;/strong&gt;: how often did you assert something wrong? (This is the one you're really managing. It should be the metric on the wall.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Recall — how many answerable cases you actually answered — becomes something you &lt;em&gt;sacrifice on purpose&lt;/em&gt;. That feels deeply uncomfortable the first time you do it, because we're trained to think coverage is the goal. It isn't. In a precision-first system, coverage is a dial you're allowed to turn &lt;em&gt;down&lt;/em&gt; to protect correctness.&lt;/p&gt;

&lt;h2&gt;
  
  
  "I don't know" is a first-class result
&lt;/h2&gt;

&lt;p&gt;In most codebases, "I couldn't determine an answer" is an afterthought — a &lt;code&gt;null&lt;/code&gt;, an empty array, a fallthrough &lt;code&gt;else&lt;/code&gt;. It's treated as the absence of a result rather than a result in its own right.&lt;/p&gt;

&lt;p&gt;In a precision-first system, abstention is a designed, named, fully-supported outcome. It has its own code path, its own logging, its own downstream handling, its own success criteria. "We looked and chose not to commit" is a &lt;em&gt;correct&lt;/em&gt; behavior, and your system should be able to say it as clearly and confidently as it says anything else.&lt;/p&gt;

&lt;p&gt;Practically, that means your core decision function doesn't return &lt;code&gt;Answer | null&lt;/code&gt;. It returns something closer to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Decision =
  | Committed(value, confidence, supporting_evidence)
  | Abstained(reason)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both branches are first-class. Both are tested. Both are observable in production. The moment "I don't know" becomes a real return type instead of a missing value, the rest of the design gets much easier to reason about — because you've stopped pretending every input deserves an output.&lt;/p&gt;

&lt;h2&gt;
  
  
  A confidence floor you do not cross
&lt;/h2&gt;

&lt;p&gt;The heart of the thing is a gate, and the gate has a non-negotiable floor.&lt;/p&gt;

&lt;p&gt;Below a certain confidence threshold, the system does not commit — full stop. Not "commits with a warning." Not "commits but flags for review later." It abstains. The floor is a hard architectural boundary, not a soft suggestion, and it is the same for everyone. No special case, no VIP path, no "just this once because the demo is tomorrow" gets to lower it.&lt;/p&gt;

&lt;p&gt;The reason this has to be structural rather than cultural is that confidence floors are exactly the thing that erodes under pressure. Someone will always have a very reasonable-sounding argument for why &lt;em&gt;this&lt;/em&gt; case should squeak through. The floor only means something if it's enforced by the system, not by the discipline of whoever is on call that week.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def decide(signals):
    score = evaluate(signals)
    if score &amp;lt; FLOOR:
        return Abstained("below confidence floor")
    if not corroborated(signals):
        return Abstained("insufficient independent support")
    return Committed(resolve(signals), score, signals)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice there are &lt;em&gt;two&lt;/em&gt; ways to abstain there, which brings us to the second principle.&lt;/p&gt;

&lt;h2&gt;
  
  
  One strong signal is not enough
&lt;/h2&gt;

&lt;p&gt;A single source being very, very sure is not the same as being right. Confident-but-wrong is the entire failure mode you're trying to eliminate, and a lone high-confidence signal is precisely how it sneaks in.&lt;/p&gt;

&lt;p&gt;So the gate asks for more than a high score — it asks for &lt;strong&gt;independent corroboration&lt;/strong&gt;. Two signals that don't share a failure mode, both pointing the same way, are worth far more than one signal shouting. The key word is &lt;em&gt;independent&lt;/em&gt;: two measurements derived from the same underlying source aren't corroboration, they're an echo. Designing for genuine independence — making sure your "second opinion" can't fail in the same way as your first — is most of the real work.&lt;/p&gt;

&lt;p&gt;This is an old idea in disguise. It's quorum. It's defense in depth. It's why critical systems use multiple sensors that fail differently. The novelty isn't the pattern; it's the discipline to apply it to &lt;em&gt;decisions&lt;/em&gt;, not just to availability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Graceful degradation, not graceful guessing
&lt;/h2&gt;

&lt;p&gt;When part of the system is unavailable — a dependency is down, a signal is missing, something times out — there's a strong temptation to "do your best with what you have." In a precision-first system, that temptation is the enemy.&lt;/p&gt;

&lt;p&gt;Degradation should make the system &lt;strong&gt;more cautious&lt;/strong&gt;, not more creative. Fewer signals available means a &lt;em&gt;higher&lt;/em&gt; bar to commit, not a lower one, because you have less ability to corroborate. The correct behavior under partial failure is to abstain more often, not to fill in the gaps with optimism. A system that gets &lt;em&gt;bolder&lt;/em&gt; as it gets blinder is a system that will eventually hurt someone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hardest part isn't technical
&lt;/h2&gt;

&lt;p&gt;Here's the thing nobody warns you about: the engineering is the easy half. The hard half is that a precision-first system will, by design, do &lt;em&gt;nothing&lt;/em&gt; in a large number of cases — and "did nothing, correctly" is a genuinely difficult thing for an organization to celebrate.&lt;/p&gt;

&lt;p&gt;Stakeholders see the abstentions and read them as missed opportunities. There's relentless gravity toward "can't we just lower the bar a little?" Every conversation, every dashboard, every incentive nudges toward more coverage. And every one of those nudges is asking you to trade away the exact property that makes the system trustworthy in the first place.&lt;/p&gt;

&lt;p&gt;So part of the architecture lives outside the code. You have to make the asymmetry &lt;em&gt;legible&lt;/em&gt;: show people the cost of a false commit in the same frame as the cost of an abstention, so that "we chose not to answer" reads as the system working, not the system failing. The floor survives only if everyone understands why it's there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;If you're building something where being wrong is worse than being silent — fraud decisions, safety interlocks, anything that touches a real person's trust — consider designing around these explicitly:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Measure precision and false-commit rate, not accuracy.&lt;/strong&gt; Put the scary number on the wall.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make abstention a first-class, designed outcome&lt;/strong&gt; — a real return type, not a &lt;code&gt;null&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enforce a hard confidence floor in the system,&lt;/strong&gt; not in the discipline of your on-call engineer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Require independent corroboration,&lt;/strong&gt; and do the hard work of making "independent" actually true.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Degrade toward caution.&lt;/strong&gt; Less information should raise the bar, never lower it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make the asymmetry visible to the org,&lt;/strong&gt; so "correctly did nothing" can be recognized as a win.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Recall is a dial. Trust is a ratchet — it only turns one way, and it turns slowly. Build like the wrong answer is the only one you can't take back, because usually, it is.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you've built systems like this, I'd love to compare notes on how you keep the confidence floor from eroding over time — that's the failure mode I find myself defending against most.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>softwareengineering</category>
      <category>backend</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Admin Panels Are Not Configuration Systems</title>
      <dc:creator>MATT ROSE</dc:creator>
      <pubDate>Sun, 14 Jun 2026 05:13:58 +0000</pubDate>
      <link>https://dev.to/matt_rose_9d0fe88d3533a4f/admin-panels-are-not-configuration-systems-22d9</link>
      <guid>https://dev.to/matt_rose_9d0fe88d3533a4f/admin-panels-are-not-configuration-systems-22d9</guid>
      <description>&lt;p&gt;I learned this the annoying way: admin panels are not configuration systems.&lt;/p&gt;

&lt;p&gt;They can become one, but not by accident.&lt;/p&gt;

&lt;p&gt;Early on, it feels harmless. Someone needs to change a headline. Someone wants to hide a section for one customer. Sales needs a different value on one account. Support needs to fix something without waiting on engineering.&lt;/p&gt;

&lt;p&gt;So a field gets added.&lt;/p&gt;

&lt;p&gt;Then another one.&lt;/p&gt;

&lt;p&gt;Then another one.&lt;/p&gt;

&lt;p&gt;At some point the admin panel becomes the place where “configuration” happens, except nothing about it is really controlled. It is just a form writing into something the product reads.&lt;/p&gt;

&lt;p&gt;That is fine until people start forgetting what the original default was. Then nobody knows whether a value is global or customer-specific, whether a saved draft is live, whether a generated record used the old value or the new one, or whether rolling something back is going to affect one account or everyone.&lt;/p&gt;

&lt;p&gt;That is when the admin panel stops being useful and starts becoming dangerous.&lt;/p&gt;

&lt;p&gt;A default workflow carries more weight than people usually admit. Support has learned it. Documentation points to it. Billing logic was built around it. Other parts of the product assume it will still be there tomorrow.&lt;/p&gt;

&lt;p&gt;So I do not treat defaults like casual settings anymore.&lt;/p&gt;

&lt;p&gt;The source should stay locked. If a customer needs something different, create an override. Duplicate the thing. Version it. Preview it. Activate it when it is ready. But do not let a customer-level edit mutate the source template.&lt;/p&gt;

&lt;p&gt;A rough version looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sourceTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;default_workflow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;locked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;sourceOfTruth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;intro&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Welcome&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;setup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Setup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customerConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customer_123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;sourceTemplateId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;default_workflow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;draft&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;overrides&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Account Setup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;review&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The original stays intact. The customer gets a different version. The admin panel edits the duplicate or the override, not the source.&lt;/p&gt;

&lt;p&gt;The status is where a lot of products get sloppy.&lt;/p&gt;

&lt;p&gt;I do not want production reading “whatever was saved last.” Saved and live are not the same thing. A draft can be saved. A preview can be rendered. An archived version can still exist for history. None of those should control production.&lt;/p&gt;

&lt;p&gt;I usually want something closer to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CONFIG_STATUS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;DRAFT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;draft&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;PREVIEW&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;preview&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ACTIVE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ARCHIVED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;archived&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Production reads active. Not latest. Not preview. Not the thing someone saved five minutes ago while they were still testing copy.&lt;/p&gt;

&lt;p&gt;That sounds strict until you have seen the alternative.&lt;/p&gt;

&lt;p&gt;The alternative is a support person saving a draft and accidentally changing what customers see. Or a sales override becoming the new default because it was easier to edit the existing object than create a customer-specific layer. Or a generated document pulling today’s value even though it was supposed to reflect what was approved last week.&lt;/p&gt;

&lt;p&gt;Those bugs are not always dramatic. Most of the time they are just messy. And messy bugs waste a lot of time because there is no single failing line of code. The product is doing exactly what it was told to do. The problem is that nobody can explain what it was supposed to do.&lt;/p&gt;

&lt;p&gt;That is why I prefer resolving configuration in one place.&lt;/p&gt;

&lt;p&gt;Do not make every component figure out which version it should use. Do not scatter account checks, copy overrides, feature flags, pricing rules, and visibility logic all over the app. That turns the product into a junk drawer.&lt;/p&gt;

&lt;p&gt;Give the app the effective config and let it render.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;resolveEffectiveConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getLockedSourceTemplate&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;activeConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getActiveCustomerConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customer&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;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;activeConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;mergeSourceWithOverrides&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;activeConfig&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 resolver should be boring. It checks for an active customer config. It ignores drafts and previews in live paths. It falls back to the locked source when nothing active exists. It merges only the allowed overrides.&lt;/p&gt;

&lt;p&gt;Everything else should be able to trust the result.&lt;/p&gt;

&lt;p&gt;The tests should be boring too.&lt;/p&gt;

&lt;p&gt;I want tests that prove drafts do not leak. I want tests that prove previews do not leak. I want tests that prove archived configs do not suddenly become live because they happened to be the last record created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;resolveLiveConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customerWithDraftOnly&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceDefault&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;resolveLiveConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customerWithPreviewOnly&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceDefault&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;resolveLiveConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customerWithArchivedOnly&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourceDefault&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;resolveLiveConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customerWithActiveConfig&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expectedOverride&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the kind of test that looks unimpressive until it saves you from a bad release.&lt;/p&gt;

&lt;p&gt;Generated records need the same discipline.&lt;/p&gt;

&lt;p&gt;If something gets generated from configuration, and that thing needs to remain historically accurate, snapshot the values. Do not keep pointing back to the live config and assume it will still mean the same thing later.&lt;/p&gt;

&lt;p&gt;Invoices, agreements, receipts, audit logs, approvals, customer confirmations — anything like that should preserve the values used when it was created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;generatedSnapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customer_123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;generatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-06-14T00:00:00Z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;configVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;v3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;setupFee&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;monthlyMinimum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;transactionSharePercent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The current config can change tomorrow. The record from yesterday should not.&lt;/p&gt;

&lt;p&gt;Otherwise history starts moving. A document that was generated with one set of values appears to have another. An invoice no longer matches what was approved. A customer asks why the number changed, and the answer is basically, “because the object was still live.”&lt;/p&gt;

&lt;p&gt;That is not a good answer.&lt;/p&gt;

&lt;p&gt;I have also become much stricter about what gets sent to the browser. The frontend usually does not need the full internal configuration object. It needs the small part required to render the page.&lt;/p&gt;

&lt;p&gt;The full object may contain pricing, internal notes, admin flags, version history, legal variables, draft states, implementation details, or things that are not technically private but still should not be sitting in the browser.&lt;/p&gt;

&lt;p&gt;So project it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;projectPublicConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;effectiveConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;effectiveConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sections&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;section&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;section&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;section&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;section&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;section&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;section&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;cta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;section&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cta&lt;/span&gt;
      &lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser gets rendering data. That is all it needed anyway.&lt;/p&gt;

&lt;p&gt;The version I trust looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;effectiveConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resolveEffectiveConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customer&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;publicConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;projectPublicConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;effectiveConfig&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;publicConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For generated records:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;effectiveConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resolveEffectiveConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customer&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;snapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createImmutableSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;effectiveConfig&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;saveGeneratedRecord&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customer&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;span class="nx"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;generatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&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;For production UI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resolveLiveConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;renderWorkflow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For preview:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;previewConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resolvePreviewConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;draftConfigId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;renderPreview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;previewConfig&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I do not like live and preview sharing the same loose path. That is how preview work becomes production behavior by accident.&lt;/p&gt;

&lt;p&gt;None of this is about making configuration fancy. I am not interested in elaborate admin systems for their own sake. The point is control.&lt;/p&gt;

&lt;p&gt;Customers will need different things. Internal teams will need to move quickly. The product will need account-level behavior eventually. That is normal.&lt;/p&gt;

&lt;p&gt;The problem starts when every one of those needs is solved by making the default more editable.&lt;/p&gt;

&lt;p&gt;If an admin panel can change the source, change what is live, change what gets generated, and expose internal state through the same path, it will eventually create a problem that is harder to unwind than it would have been to model correctly in the first place.&lt;/p&gt;

&lt;p&gt;The questions I want the system to answer are simple:&lt;/p&gt;

&lt;p&gt;What is the locked default?&lt;/p&gt;

&lt;p&gt;What is the active customer override?&lt;/p&gt;

&lt;p&gt;What is only a draft?&lt;/p&gt;

&lt;p&gt;What values were used when this record was generated?&lt;/p&gt;

&lt;p&gt;What is safe to send to the client?&lt;/p&gt;

&lt;p&gt;If those answers are not clear, the product does not really have a configuration layer. It has a form connected to production.&lt;/p&gt;

&lt;p&gt;That might be acceptable for a while. Early products make tradeoffs. I understand that.&lt;/p&gt;

&lt;p&gt;But once the system touches real customers, real billing, generated records, or account-specific behavior, the boundary has to get stricter.&lt;/p&gt;

&lt;p&gt;Lock the source. Duplicate before editing. Preview before activation. Resolve active config only. Snapshot generated outputs. Send the client only what it needs.&lt;/p&gt;

&lt;p&gt;That is the version I trust.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>softwareengineering</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Stop Trusting Every WHERE Clause: Tenant Isolation in PostgreSQL</title>
      <dc:creator>MATT ROSE</dc:creator>
      <pubDate>Sat, 13 Jun 2026 01:55:00 +0000</pubDate>
      <link>https://dev.to/matt_rose_9d0fe88d3533a4f/the-multi-tenant-fortress-bank-grade-data-isolation-in-postgresql-1f6i</link>
      <guid>https://dev.to/matt_rose_9d0fe88d3533a4f/the-multi-tenant-fortress-bank-grade-data-isolation-in-postgresql-1f6i</guid>
      <description>&lt;p&gt;Multi-tenant apps usually start with a simple rule:&lt;/p&gt;

&lt;p&gt;Every query needs to be scoped to the current tenant.&lt;/p&gt;

&lt;p&gt;That works fine until it doesn't.&lt;/p&gt;

&lt;p&gt;Someone writes a new endpoint. Someone adds a reporting query. Someone ships a quick admin tool. One missing &lt;code&gt;WHERE tenant_id = ...&lt;/code&gt; later, one account can see data that belongs to another account.&lt;/p&gt;

&lt;p&gt;That's the kind of bug that can end a product.&lt;/p&gt;

&lt;p&gt;I don't like leaving tenant isolation entirely up to application code. The app should still scope queries correctly, but the database should also enforce the boundary. If the database is the last place where the data lives, it shouldn't blindly trust every query that reaches it.&lt;/p&gt;

&lt;p&gt;PostgreSQL Row-Level Security gives you a way to push that boundary down into the database.&lt;/p&gt;

&lt;h2&gt;
  
  
  The basic idea
&lt;/h2&gt;

&lt;p&gt;Row-Level Security, or RLS, lets PostgreSQL decide which rows are visible for a given query.&lt;/p&gt;

&lt;p&gt;Instead of relying only on code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customer_records&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;tenant_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you can make the table itself enforce the tenant boundary.&lt;/p&gt;

&lt;p&gt;That means a careless query like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customer_records&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;doesn't automatically return everything.&lt;/p&gt;

&lt;p&gt;The database checks the active tenant context first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a tenant anchor
&lt;/h2&gt;

&lt;p&gt;Every tenant-owned table needs an anchor column. In most systems that is something like &lt;code&gt;tenant_id&lt;/code&gt;, &lt;code&gt;account_id&lt;/code&gt;, or &lt;code&gt;organization_id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's a small example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;tenants&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;customer_records&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;tenant_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;tenants&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;full_name&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part isn't the table name. The important part is that each row belongs to a tenant.&lt;/p&gt;

&lt;p&gt;Now enable RLS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;customer_records&lt;/span&gt; &lt;span class="n"&gt;ENABLE&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt; &lt;span class="k"&gt;LEVEL&lt;/span&gt; &lt;span class="k"&gt;SECURITY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once RLS is enabled, PostgreSQL will apply policies before returning rows. Without a policy, the table is effectively locked down for normal access.&lt;/p&gt;

&lt;p&gt;That default-deny behavior is the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pass tenant context into the transaction
&lt;/h2&gt;

&lt;p&gt;The database needs to know which tenant is making the request.&lt;/p&gt;

&lt;p&gt;I usually prefer setting that inside the transaction, then letting the policy read it.&lt;/p&gt;

&lt;p&gt;Example using Node.js and &lt;code&gt;pg&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getCustomerRecordsForTenant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;BEGIN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT set_config('app.current_tenant_id', $1, true)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM customer_records&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;COMMIT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ROLLBACK&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customer_records&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's no &lt;code&gt;WHERE tenant_id = ...&lt;/code&gt; in that query.&lt;/p&gt;

&lt;p&gt;The filtering is not gone. It moved into the database policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write the RLS policy
&lt;/h2&gt;

&lt;p&gt;Now connect the row’s &lt;code&gt;tenant_id&lt;/code&gt; to the tenant context set on the session.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;POLICY&lt;/span&gt; &lt;span class="n"&gt;tenant_isolation_policy&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;customer_records&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;
&lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;tenant_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_setting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'app.current_tenant_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="n"&gt;UUID&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;tenant_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_setting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'app.current_tenant_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="n"&gt;UUID&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;USING&lt;/code&gt; clause controls which existing rows are visible.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;WITH CHECK&lt;/code&gt; clause controls which rows can be inserted or updated.&lt;/p&gt;

&lt;p&gt;So if the active tenant is Tenant A, the database should only expose rows for Tenant A. It should also reject writes that try to create or move records into another tenant.&lt;/p&gt;

&lt;p&gt;Read isolation and write isolation are separate problems. You need both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is safer than app-only filtering
&lt;/h2&gt;

&lt;p&gt;Application-layer filtering depends on every developer remembering to write the right condition every time.&lt;/p&gt;

&lt;p&gt;Database-enforced isolation gives you a second boundary.&lt;/p&gt;

&lt;p&gt;If someone writes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customer_records&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the policy still applies.&lt;/p&gt;

&lt;p&gt;If someone writes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customer_records&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the policy still applies.&lt;/p&gt;

&lt;p&gt;If the tenant context is missing, the comparison does not match tenant-owned rows, so the query should fail closed instead of exposing everything.&lt;/p&gt;

&lt;p&gt;That does not mean RLS is magic. You still need to be careful with privileged roles, service-role access, migrations, background jobs, and admin tooling. A bad bypass can still cause damage.&lt;/p&gt;

&lt;p&gt;But RLS changes the default failure mode. A missing application filter no longer has to mean cross-tenant exposure.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few rules I would not skip
&lt;/h2&gt;

&lt;p&gt;Use a tenant context that's set inside the transaction. Don't rely on a loose global variable in application memory.&lt;/p&gt;

&lt;p&gt;Keep service-role or admin access away from normal request paths. If you have a role that bypasses RLS, treat it like a dangerous tool.&lt;/p&gt;

&lt;p&gt;Test the policies directly. Do not only test the API layer.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;set_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'app.current_tenant_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'TENANT_A_UUID'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customer_records&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then repeat with a different tenant and confirm the rows change.&lt;/p&gt;

&lt;p&gt;Also test writes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;customer_records&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'TENANT_B_UUID'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Test User'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'test@example.com'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the session is scoped to Tenant A, that insert should not be allowed for Tenant B.&lt;/p&gt;

&lt;p&gt;Those tests are boring. They are also the kind of boring that prevents ugly production incidents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the database as a backstop
&lt;/h2&gt;

&lt;p&gt;I still write scoped application queries. I still validate permissions in the API. I still treat authorization as an application concern.&lt;/p&gt;

&lt;p&gt;But I don't want the database to be passive.&lt;/p&gt;

&lt;p&gt;In a multi-tenant system, the database should know enough to say no.&lt;/p&gt;

&lt;p&gt;RLS is not a replacement for good application design. It is a backstop for the day someone writes the wrong query, ships the wrong report, or builds an admin endpoint too quickly.&lt;/p&gt;

&lt;p&gt;That day comes eventually.&lt;/p&gt;

&lt;p&gt;When it does, I would rather have PostgreSQL enforce the tenant boundary than hope every &lt;code&gt;WHERE&lt;/code&gt; clause was perfect.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>database</category>
      <category>postgres</category>
      <category>security</category>
    </item>
    <item>
      <title>The Catch and Release Pattern: Handling High-Volume Webhooks in Node.js</title>
      <dc:creator>MATT ROSE</dc:creator>
      <pubDate>Sat, 13 Jun 2026 01:38:15 +0000</pubDate>
      <link>https://dev.to/matt_rose_9d0fe88d3533a4f/the-catch-and-release-pattern-handling-high-volume-webhooks-in-nodejs-17d2</link>
      <guid>https://dev.to/matt_rose_9d0fe88d3533a4f/the-catch-and-release-pattern-handling-high-volume-webhooks-in-nodejs-17d2</guid>
      <description>&lt;p&gt;If you are building an API that integrates with third-party vendors, you will eventually face the webhook flood. &lt;/p&gt;

&lt;p&gt;When an external service sends a massive spike of webhook events, the standard approach of processing the data and inserting it into a database synchronously will block the Node.js event loop. Your API will time out, the vendor will assume the delivery failed, and you will drop critical data.&lt;/p&gt;

&lt;p&gt;To survive unpredictable traffic spikes, you need to decouple the HTTP response from the data processing. Here is how to implement the "Catch and Release" pattern using Node.js, Express, and BullMQ.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Node.js and Express installed.&lt;/li&gt;
&lt;li&gt;A running instance of Redis (required for BullMQ).&lt;/li&gt;
&lt;li&gt;Basic understanding of asynchronous JavaScript.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Synchronous Trap (What Not to Do)
&lt;/h2&gt;

&lt;p&gt;Most developers write their first webhook receiver like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/webhook/inventory&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ❌ Anti-pattern: Heavy processing before responding&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;normalizedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;heavyDataTransformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&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;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;normalizedData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Vendor waits for the database to finish...&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; If the vendor sends 500 webhooks a second and your database takes 200ms to insert a record, the database connection pool will max out. Requests will queue up, memory will spike, and the connection will close. The data is gone forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Implementing "Catch and Release"
&lt;/h2&gt;

&lt;p&gt;The golden rule of webhook ingestion is to acknowledge receipt immediately. We want to return a &lt;code&gt;200 OK&lt;/code&gt; or &lt;code&gt;202 Accepted&lt;/code&gt; status back to the vendor &lt;em&gt;before&lt;/em&gt; we do any heavy lifting.&lt;/p&gt;

&lt;p&gt;To do this safely without losing the data in memory if the server crashes, we push the raw payload to a persistent background queue.&lt;/p&gt;

&lt;p&gt;First, install BullMQ and Redis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;bullmq ioredis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, configure the queue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Queue&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bullmq&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Redis&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ioredis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Connect to Redis&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;redisConnection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIS_URL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Create the ingestion queue&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;webhookQueue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;webhook-ingestion&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="na"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;redisConnection&lt;/span&gt; 
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, rewrite the Express route to catch the payload, queue it, and release the connection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/webhook/inventory&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. Push raw data to Redis immediately&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;webhookQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;process-inventory&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;backoff&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exponential&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Release the vendor connection instantly&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accepted for processing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed to queue webhook&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Internal Server Error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this pattern, your Express server can handle thousands of requests per second. The route does nothing but write JSON to Redis, which is incredibly fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Processing the Queue Safely
&lt;/h2&gt;

&lt;p&gt;Now that the data is safely persisted in Redis, we can process it at our own pace using a BullMQ Worker. This worker runs on a separate thread (or an entirely separate server) so it never blocks our Express API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Worker&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bullmq&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;webhook-ingestion&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;job&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;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;job&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="c1"&gt;// Now we can safely perform heavy processing&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;normalizedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;heavyDataTransformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// If the database is locked, it throws an error, &lt;/span&gt;
  &lt;span class="c1"&gt;// and BullMQ automatically retries based on our backoff strategy.&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;normalizedData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;redisConnection&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;completed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Job &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;job&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;span class="s2"&gt; processed successfully`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Job &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;job&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;span class="s2"&gt; failed:`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&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;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By implementing the Catch and Release pattern, you separate the HTTP transport layer from your business logic. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Express&lt;/strong&gt; acts purely as a lightning-fast catcher's mitt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis/BullMQ&lt;/strong&gt; acts as the shock absorber, holding the data safely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Worker&lt;/strong&gt; acts as the engine, processing data only as fast as your database can handle it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This architecture ensures zero data loss, prevents database exhaustion, and keeps external vendors happy with lightning-fast response times.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>softwareengineering</category>
      <category>backend</category>
      <category>systemdesign</category>
    </item>
  </channel>
</rss>
