
What the AI crawlers actually fetched from this site in one week
SAGARIS5 min
BlogEngineering
A consent gate is only real when you know what it does with an answer it cannot determine, and what that costs you in your own dashboards.

Most consent implementations answer two questions well and a third one badly. Did this person say yes? Did they later say no? Both are lookups, and both are easy. The third question is what the system does when it cannot determine the answer at all, and that is where the design actually lives.
An unresolvable answer is not rare. A caller withholds their number. A visitor arrives with storage partitioned so nothing can be read. A record exists but the field that would classify it is empty. In each case some code has to choose between proceeding and stopping, and the choice usually gets made by whoever wrote the default, in an afternoon, without anybody calling it a policy decision.
Fail-open is the overwhelmingly common default, and it does not look like a decision. It looks like a null check that returns early. The system proceeds, nothing appears in a log as a refusal, and the first person to learn that the gate has a hole in it is a regulator or a journalist.
Whether a call may be recorded depends on where the parties are. Some United States states require every party to consent; others require only one. That means a recording decision starts with a geography lookup, and a geography lookup can fail.
The policy module that makes the decision is deliberately pure. It performs no network and no database access; any lookup that would hit a service is supplied by the caller as an injected function, so the decision itself is deterministic and testable. Its default configuration sets the unknown-jurisdiction policy to strict, which means a jurisdiction that is null, or that is present in neither reference set, is treated as all-party. Unknown becomes the strictest reading rather than the most convenient one.
For inbound calls the bar is stricter still, and the comment explaining why is the sharpest sentence in the file. The disclosure gate fails closed to all-party on an unresolved number and still returns a recording-allowed verdict behind a generic spoken notice, which the code calls right for transcription and wrong for a recording file. So the layer above it overrides the permission it was just given: no resolved state, no recording. Two fail-closed decisions in sequence, the second one refusing something the first one allowed.
There is a relaxation, and hiding it would undercut the whole argument. A single environment flag lets an unresolved jurisdiction arm recording, so that United States test calls placed from withheld or international caller IDs can be exercised. It is fail-closed in code, where only an explicit truthy token opens it and anything absent, empty or malformed reads as false, and it is written as an explicit false in the committed deploy configuration. Even fully open it changes one thing: an unresolved jurisdiction may arm recording after a disclosure has been spoken and verified, never immediately. The file says in capital letters that it is a temporary testing toggle and must be routed past counsel before it is enabled anywhere real.
Here is the part that usually gets left out of writing like this.
Fail-closed gates fail on their own lookups, and when they do, the product stops working. The defect that produced the current design was exactly that: the area code to state map was hand-typed, 131 entries covering 14 states. With that map in place the gate could not resolve a one-party jurisdiction for most of the country, so it did what it was built to do and refused. It blocked recording on every call.
That is the real trade. A fail-closed control converts a compliance risk into an availability risk, and if your reference data is bad, you have not built a careful product, you have built one that does nothing while looking principled.
The fix was not to loosen the gate. It was to stop typing the data. The map is now generated from the North American Numbering Plan Administrator's NPA registry, the authoritative assigner of area codes to geographies. The generator refuses to fetch the CSV itself, because a silently stale or silently empty download is the exact failure mode being designed out; you download it explicitly and pass the path. It asserts that the set of United States locations the registry reports equals the set that has been classified, and it fails rather than letting an unclassified jurisdiction drift into the recording-allowed bucket by default.
And it is honest about its own reach. It is classified tier 1 rather than tier 0, because it detects drift from the registry and cannot detect a wrong one-party or all-party legal classification. That classification is a separate control. A guard that claimed to cover the legal question would be worse than the hand-typed map, because it would be trusted.
The same posture applied to ourselves costs us something we would rather have.
PostHog and Google Analytics 4 are both mounted in the root layout and both read one consent hook. Until a visitor explicitly allows analytics, PostHog does not initialize, so it sets no cookies and captures nothing: no autocapture, no pageviews, no page leaves. GA4 is stricter still, because gtag sets cookies and sends a hit the moment it evaluates. The provider renders no script tag at all until consent is granted. No script tag, no cookie, no network request. The file names the weaker alternative explicitly: loading the script and then telling it consent: denied is a different and weaker posture.
Unknown consent fails closed. If storage is blocked, if the stored value is garbage, if the write that records a choice throws, the gate stays shut, because an unreadable choice reads as no consent. Declining the banner and ignoring the banner produce identical behaviour, which is the design the banner text promises: no analytics run unless you allow them, and declining costs no functionality.
Every figure those two products report about this site counts only the visitors who clicked Allow.
The gap is not a known percentage we are declining to publish. It is unknowable from those products, and the reason is structural: a visitor who declines or ignores the banner generates no events, so the tools that would size the missing population are the same tools the missing population is missing from. There is no denominator to divide by. A funnel measured this way is a floor on an unknown total, and treating it as a rate is an error we would be making about ourselves.
Two smaller costs, for completeness. The choice lives in localStorage, so it is a record about a browser and not about a person: the same visitor on a phone and a laptop is two subjects, prompted twice, and if they answer differently they are counted differently. And the banner only appears when PostHog is configured, which means a misconfiguration produces silence rather than an error. Silence is the safe direction and it is still a way to be wrong.
We think that is the correct trade, and we would rather say what it costs than quietly enjoy a bigger number. But notice how easy the opposite is. A vendor whose consent gate fails open has better analytics than we do, and there is nothing in either dashboard that shows you which one you are looking at.
There are two, and the second is the one that discriminates.
Ask what the consent gate does when the answer cannot be determined. Everyone has a good response to that; it is a design question and it is easy to answer well in a meeting.
Then ask what that decision costs them in their own reporting. A fail-closed gate takes something away from the operator who built it: recordings that could have been made, visits that will never appear, a lookup table that must be right or the product stops. A gate that has cost its owner nothing is worth pressing on, because either it has never met an unresolved case, which is unlikely, or it resolved those cases in the direction that cost nothing.
Thirty minutes, your own data, no setup.
SAGARIS opens fully in October 2026. Join the waitlist and we will be in touch before launch.