BuilderProof editorial team21 min read10 views

What Runs Before Anyone Agrees? A Proposed Axis for Consent Gating in Generated Apps (September 2026)

Every measurement a developer makes of their own generated app is taken in a browser profile that has already agreed once. A pre-registration for a consent-gating axis, with the platform documents that make the defect reachable by accident.

Updated on September 24, 2026

Flat diagram on a cream background: a navy rounded-rectangle outline stands for an empty browser window, with one solid navy bar near its lower inside edge. Three amber arrows angle up and right from inside it and cross out through its top-right corner.
Flat diagram on a cream background: a navy rounded-rectangle outline stands for an empty browser window, with one solid navy bar near its lower inside edge. Three amber arrows angle up and right from inside it and cross out through its top-right corner.
On this page

Quick answer (September 2026): nobody has measured what an AI-generated application does in the window before a visitor has agreed to anything, and this page does not measure it either. What follows is a pre-registration: a proposed axis, the weights we would use, the failing case for each signal, and the exact protocol we would run. No builder is scored here and none is named in a posture.

The short version of the finding that motivated it. The rule that governs this behaviour is not about cookies, the most widely quoted wording of it has been superseded since 2009, and the two documents a generated application is actually assembled from, the framework's third-party integration guide and the analytics vendor's consent guide, describe the same integration while only one of them mentions consent at all.

The rule is not about cookies, and the version most people quote is the wrong one

We checked both texts directly rather than citing a summary, because the two differ on the thing that matters most.

The original 2002 text of Article 5(3) of Directive 2002/58/EC is an opt-out rule. It permits storage or access on condition that the user "is offered the right to refuse such processing by the data controller" (Directive 2002/58/EC, original text, CELEX 32002L0058).

The consolidated text in force, as amended by Directive 2009/136/EC and marked as amendment M2 in the consolidated version, is an opt-in rule:

Article 5(3), consolidated, verbatim: "Member States shall ensure that the storing of information, or the gaining of access to information already stored, in the terminal equipment of a subscriber or user is only allowed on condition that the subscriber or user concerned has given his or her consent, having been provided with clear and comprehensive information ... about the purposes of the processing. This shall not prevent any technical storage or access for the sole purpose of carrying out the transmission of a communication over an electronic communications network, or as strictly necessary in order for the provider of an information society service explicitly requested by the subscriber or user to provide the service." (consolidated text, CELEX 02002L0058-20091219)

Two properties of that sentence decide the whole shape of this axis, and neither of them is the one a cookie banner expresses.

It keys on terminal equipment, not on cookies. The trigger is storing information, or reading information already stored, on the visitor's device. The word cookie does not appear. A generated application that moves its analytics identifier from a cookie into localStorage or IndexedDB has changed the storage mechanism and has not changed the trigger at all. This is the first reason a rubric here has to be written in terms of device storage rather than in terms of cookies.

It can fail to trigger entirely. A design that stores nothing on the device and reads nothing already stored is outside the sentence, not inside it with a defence. That case is real and documented, and we get to it below.

Those two consequences point in opposite directions, which is exactly why this is measurable. An application can ship a prominent cookie banner over an analytics product that touches no device storage, and an application can ship no banner at all while writing a durable identifier to localStorage on first paint. The banner is not a proxy for the behaviour in either direction.

Vercel

The framework side is a single component. Next.js documents @next/third-parties and describes the container integration in one sentence: "The GoogleTagManager component can be used to instantiate a Google Tag Manager container to your page. By default, it fetches the original inline script after hydration occurs on the page." The recommended placement is the root layout, "To load Google Tag Manager for all routes, include the component directly in your root layout and pass in your GTM container ID", and the documented example is three lines inside the html element (Next.js, Third Party Libraries, last updated June 1, 2026).

We counted the guide's own vocabulary. The word consent appears zero times in that document, in either case. So does GDPR, and so does privacy in any sense other than a footer link. The component's documented options cover the container id, the data layer and server-side tagging. There is no parameter anywhere in its published API for whether the visitor has agreed to anything.

Google

The vendor side says the opposite thing about the same integration. Google's consent mode guide opens by stating that the default position is no position at all: "Set a default value for each consent type you are using. By default, no consent mode values are set." It then places a hard ordering requirement on the page: "call the gtag('consent', 'default', ...) command on every page of your site before any commands that send measurement data (such as config or event)" (Google, Set up consent mode on websites).

Put the two documents next to each other and the composition is visible in a way neither of them states. The framework's recommended integration is a component mounted in the root layout, so by construction it runs on every route for every visitor. The vendor's requirement is that a consent default be declared before that component's script sends anything. Nothing in the framework component accepts that declaration, and nothing in the vendor guide knows the component exists. Each document is correct about itself. Neither is responsible for the seam, and a code generator writing the obvious integration lands exactly in it.

The same guide is also candid that the ordering is a race rather than a guarantee: "If your banner loads asynchronously, it might not always run before your Google tags." Its own remedy is a stopwatch, a wait_for_update value in milliseconds, with 500 given as the worked example. A remedy expressed as a timeout is an admission that the two loads are not ordered by anything structural.

The vendor documents that the answer is not remembered

This is the signal we would weight third, and it exists because the behaviour is documented rather than suspected.

Google, consent mode, verbatim: "Since consent mode doesn't save consent choices, update the consent status as soon as a user interacts with your consent management solution. After a user grants consent, persist their choice and call the update command accordingly on subsequent pages."

Persistence is the application's job, stated plainly by the party whose tag it is. The generated failure mode follows directly. A banner that sets a flag, hides itself and never reads that flag again produces an application where the first page view runs under the visitor's answer and every page view after it runs under the vendor default. Nothing looks broken. The banner is correctly absent on the second load, because it remembers that it was dismissed. What it does not do is re-apply what was dismissed to.

That is a defect a reviewer cannot see by looking at the second page, because the second page is the one where the banner has correctly gone away.

What counts as an answer, and what does not

Four properties, all from published text, all testable in an emitted application.

It has to be an act. Consent is defined as "any freely given, specific, informed and unambiguous indication of the data subject's wishes by which he or she, by a statement or by a clear affirmative action, signifies agreement" (GDPR, Article 4(11)). Recital 32 removes the ambiguity about what is not one: "Silence, pre-ticked boxes or inactivity should not therefore constitute consent." (Recital 32)

It has to be per purpose. The same recital: "When the processing has multiple purposes, consent should be given for all of them." A single button covering analytics, advertising and personalisation is not an expressible answer for a visitor who would accept one of the three.

It has to be demonstrable. "Where processing is based on consent, the controller shall be able to demonstrate that the data subject has consented" (GDPR, Article 7). An application that acts on an answer and records nothing about it cannot produce the answer later.

It has to be reversible at the same cost. Article 7 again, and this is the property generated applications fail most often: "The data subject shall have the right to withdraw his or her consent at any time", and "It shall be as easy to withdraw as to give consent." Almost every generated banner we have read the shape of has an accept control on first visit and, once dismissed, offers no route anywhere in the finished application back to the state it was dismissed from.

The machine-readable signal is ambiguous in the one direction that matters

MDN

There is a browser-level preference an application could read instead of guessing. MDN documents Navigator.globalPrivacyControl as reflecting the Sec-GPC header and returning "true if the user explicitly does not provide consent to sell or share their data" and "false if the user either grants consent, or has not indicated a preference" (MDN, Navigator: globalPrivacyControl).

Read the false branch again. It is returned both by a visitor who agreed and by a visitor who has never been asked. The signal collapses agreement and silence into one value, which is the precise collapse the consent definition above forbids an application from making. An application that reads false and proceeds has read silence as permission, and it has done so using a purpose-built privacy API.

MDN also labels the property "Experimental: This is an experimental technology" and carries the availability banner verbatim: "This feature is not Baseline because it does not work in some of the most widely-used browsers." So an axis cannot require it, and we would not. What an axis can score is narrower and fair: whether an application that reads the signal at all treats false as a grant.

The case where the rule does not fire, and why the rubric must not punish it

Vercel

Vercel documents its own analytics product as working without device storage: "Vercel Web Analytics allows you to track your website traffic and gather valuable insights without using any third-party cookies, instead end users are identified by a hash created from the incoming request." It also states the collection is unconditional: "All page views will automatically be tracked by Vercel Web Analytics, including both fresh page loads and client-side page transitions." The word consent appears zero times on that page (Vercel, Web Analytics privacy and compliance).

That silence is not an oversight, and the axis should say so out loud. Article 5(3) is triggered by storing information on, or reading information from, the terminal equipment. A server-side hash of an incoming request does neither. The document is silent about consent because on the trigger it is describing, there is nothing for consent to attach to.

This is the single most important calibration in the rubric. A naive scoring rule that rewards the presence of a banner would mark down a design that avoided the storage entirely, and mark up a design that wrote a durable identifier to localStorage and then asked about cookies. The signal has to be written against device storage and third-party reach, never against the presence of a dialog.

Proposed rubric

Seven signals, one hundred points. Weights are a proposal and are argued below.

Scroll to see more

SignalWeightWhat a failing case looks like
Nothing non-essential runs before an answer exists22On a first visit in a clean profile, with nothing clicked, the tag container loads, the analytics script executes and at least one third-party host is contacted
The gate is in the code path, not in the interface20A banner renders and dismisses correctly while the tracking component is mounted unconditionally in the root layout, so the control changes which pixels are drawn and not which requests are sent
The answer is persisted and re-applied on every load16A flag is written on click and never read again, so the second page view runs under the vendor default while the banner is correctly absent
Withdrawal is as reachable as agreement14There is an accept control on first visit and no control anywhere in the finished application that returns the visitor to the state before it
The answer is granular by purpose12One control covers analytics, advertising and personalisation together, so a visitor who would accept one of the three has no expressible answer
Scope is written against device storage, not against cookies9The disclosure names cookies while the application writes its identifier to localStorage, so the thing described and the thing stored are different things
The answer is recorded, not merely acted upon7Nothing in the emitted application can state what was agreed, when, or against which version of the text

Four postures

The ladder is deliberately uneven. The step from Level 1 to Level 2 is where nearly all of the value sits, and the rubric should not let a Level 1 accumulate enough partial credit to look like a Level 2.

Level 0, Ungated. Nothing asks and everything runs. The emitted application contacts its third parties on first paint and writes whatever it writes.

Level 1, Announced. A banner exists. It renders, it dismisses, it may even persist its own dismissal. It does not gate the code path, so every request that would have been sent is sent anyway. This is the level the documented facts above make reachable by accident, because the framework component that loads the tag and the component that renders the banner are independent and neither one knows about the other.

Level 2, Gated. The answer controls what loads. The tag is mounted conditionally, the third-party host is not contacted before the answer exists. Persistence, granularity and withdrawal are still absent or partial.

Level 3, Accountable. Gated, persisted across loads and routes, granular by purpose, withdrawable by a control that is present in the finished application, and recorded in a form the operator could produce later.

Protocol we would run

  1. Generate from a reference prompt that describes features only. It must not mention consent, cookies, privacy or any regulation. We are borrowing the reasoning our own deletion and data-retention axis published for the same decision: a prompt that names the duty measures instruction-following rather than default posture, and default posture is the interesting case.
  2. Take a sink census from the emitted source. Enumerate every third-party host the application can reach, every storage API it calls, and every place a tag component is mounted.
  3. Record the control that carries no information, and record it as such: load the application a second time in the same profile. This is the measurement the build loop actually makes, it will look clean on almost every entrant, and it is evidence of nothing. It is in the protocol so that its result is written down next to the results that do discriminate.
  4. Load the application in a genuinely fresh browser profile. Click nothing. Record every network request by host and every write to cookies, localStorage, sessionStorage and IndexedDB for thirty seconds.
  5. Decline, or dismiss without accepting, and repeat the recording. A Level 1 is identified here: the request set does not change.
  6. Navigate to a second route inside the application and repeat the recording. This separates a gate that holds from a gate that only ran once.
  7. Reload the first route in the same profile and repeat. This is the persistence probe, and the vendor's own documentation above predicts what a failing entrant does here.
  8. Search the emitted source for any route, control or settings surface that returns a visitor to the pre-answer state. Absence is the finding; a link to an external policy page is not a withdrawal path.
  9. Probe the storage shape. Record whether identifiers land in cookies, in localStorage or in IndexedDB, and compare that against whatever the application's own disclosure text names.
  10. Record provenance: the exact export or commit, the builder version string if one is emitted, and the date. Every number above is a point-in-time read of a moving target.

The trap this axis names: the already-accepted illusion

Every measurement available on the build loop is taken after the measurer has already agreed.

The developer opens the preview, a banner appears once, they click it to get it out of the way, and from that moment their browser profile carries the answer. Every subsequent load, every screenshot, every demo and every review happens in a profile that has consented. The pre-answer state is not rare and it is not intermittent. It is the state every real first-time visitor is in, and the one state the person building the application destroyed on first contact.

This belongs to a family we have named repeatedly on this site, where the only cheap evidence available at test time is generated by a single actor. The cold start axis named a version of it where the act of observing prevents the condition, because a developer refreshing a page keeps the instance warm. This one is sharper in a specific way, and the difference is worth stating because it changes the remedy.

Idleness regenerates. Walk away for fifteen minutes and the cold-start condition returns on its own, so the defect is merely unobserved rather than unreachable. A fresh profile does not regenerate. Once a profile has answered, it never returns to not having answered unless somebody deliberately destroys it. The condition is consumed once, permanently, by the first person to look, which is why the protocol above has to specify a new profile at step 4 and why an ordinary careful reviewer will never see the defect no matter how carefully they look.

Why this is not already covered

Three axes in this series come close enough that the boundary has to be drawn explicitly, in both directions, or the axis is not worth proposing.

Against security headers and response hardening

Our security headers axis scores cookie attributes among other response directives: whether a cookie carries HttpOnly, Secure and a SameSite value. This axis scores whether the cookie is set at all, and whether anything preceded it.

Opposite directions, and both are real. An application can set a perfectly attributed analytics cookie, host-prefixed and correctly scoped, on first paint, before any visitor has been asked anything. It scores at the top of the headers axis and at the bottom of this one. The reverse also holds: an application can gate every non-essential load behind a granular, persisted, withdrawable answer, and then set the resulting preference cookie with no flags at all, so the one cookie the visitor deliberately created is the one an injected script can read.

Against deletion and data-retention posture

Our deletion and data-retention axis carries a third-party fan-out signal whose failing case reads, in its own words, that the local row is deleted while the external customer, mailing-list and analytics records the app created are left in place. That signal scores getting the records removed. It assumes, correctly for its own purpose, that they exist.

This axis asks the question one step earlier: whether the record should have been created, and whether anything was asked before it was. The two move independently. An application can fan a deletion out flawlessly to every third party it ever wrote to, scoring at the top of that axis, while having written to all of them on a first visit with nothing asked. And an application can gate every non-essential collection perfectly and then delete nothing at all when an account is closed.

Against multi-tab state coherence

This one is not a boundary. It is a composition, and saying so is more useful than drawing a line that is not there.

Our multi-tab state coherence axis scores whether a second open document finds out about a change made in the first. A consent answer persisted on the client is exactly the kind of state that raises that question. A visitor who withdraws in one tab, on an application with a working withdrawal control and no cross-document propagation, is looking at one document that has stopped collecting and another that has not, both of which believe they are correct. The remedy for that belongs to the other axis. The reason it matters belongs to this one, and neither axis reaches the failure on its own.

Why we are naming a pattern and not publishing placements

In August 2026 this lab withdrew its June output-quality result set and the composite that depended on it, because the underlying runs could not be reproduced from retained evidence. That withdrawal has a consequence we intend to honour consistently.

An axis proposal is a pre-registration, not a report. The rubric above has not been executed. No builder has been assigned a level. No score exists. Where this page describes cohort behaviour it describes a pattern that published platform documentation makes possible, never a measurement of who does what. A reader should not infer from the specificity of the protocol that a run has happened. It has not.

Limitations and open questions

  • This is not a legal compliance assessment. It is a technical measurement of what an application does before a choice exists. The anchor texts are European because they are published, specific and stable, which makes them useful for writing a failing case. Anchoring a signal to a text is not a claim that any application is or is not lawful anywhere, and it is not advice.
  • Jurisdiction. The same caveat our deletion axis recorded applies here with more force, because consent rules diverge more across jurisdictions than erasure rules do. A reasoned argument for scoring against a different or a plural standard would change several signals, and we would rather have it before scores exist.
  • The exempt set is not scored, deliberately. The consolidated text exempts storage that is "strictly necessary in order for the provider of an information society service explicitly requested by the subscriber or user to provide the service". Deciding what is strictly necessary for a given generated application is a judgement we do not think a rubric can make from the outside, so signal one is scoped to what the emitted source itself labels as analytics, advertising or third-party embedding.
  • Signal six can punish the better design if read carelessly. A design that stores nothing on the device is outside the trigger, and it should not lose points for a disclosure it does not need. The signal scores a mismatch between what the application says it stores and what it stores, not the absence of a banner.
  • Browser support limits the machine-readable half. Navigator.globalPrivacyControl is labelled Experimental and is documented as not Baseline, so nothing in the rubric requires it. Scoring how an application treats the signal when it is present is fair; requiring the signal is not.
  • Single-generation variance. One export is one sample, and this axis is unusually prompt-sensitive because the topic is one a model may volunteer. A builder that is inconsistent between generations needs a multi-run design before scoring is fair.
  • Weight subjectivity. The 22/20/16/14/12/9/7 split is a proposal. A reasoned case that withdrawal should outweigh persistence, on the grounds that an unpersisted answer is recoverable by asking again and an unwithdrawable one is not, would change the ranking, and we would rather have that argument now than after the numbers exist.

Comments, counter-rubrics and reproduction attempts are welcome. A run of this protocol that contradicts what this page predicts is the single most useful thing you can send us.

References

Cite this benchmark

Plain text
BuilderProof editorial team. "What Runs Before Anyone Agrees? A Proposed Axis for Consent Gating in Generated Apps (September 2026)". BuilderProof, September 2026. https://www.builderproof.org/benchmarks/what-runs-before-anyone-agrees-consent-gating-axis-september-2026.
BibTeX
@misc{builderproof-what-runs-before-anyone-agrees-consent-gating-axis-september-2026,
  title  = {{What Runs Before Anyone Agrees? A Proposed Axis for Consent Gating in Generated Apps (September 2026)}},
  author = {{BuilderProof editorial team}},
  year   = {2026},
  month  = {sep},
  howpublished = {\url{https://www.builderproof.org/benchmarks/what-runs-before-anyone-agrees-consent-gating-axis-september-2026}},
  note   = {BuilderProof, builderproof.org}
}

Frequently asked questions

Is this a legal compliance test?

No, and the page says so in its limitations. It is a technical measurement of what an application loads, stores and contacts before a visitor has answered anything. The anchor texts are European because they are published, specific and stable, which makes them usable for writing a failing case. Anchoring a signal to a text is not a claim that any application is or is not lawful anywhere.

Why is the rubric written against device storage rather than cookies?

Because the rule is. The consolidated text of Article 5(3) is triggered by the storing of information, or the gaining of access to information already stored, in the terminal equipment of a subscriber or user. The word cookie does not appear in it. Moving an identifier from a cookie into localStorage changes the mechanism and not the trigger, and a design that stores nothing on the device may not be inside the sentence at all. A rubric keyed on cookies would mark both of those cases the wrong way round.

Does a banner mean an application passes?

No, and separating those two things is most of the point. The proposed ladder puts a banner that renders and dismisses without gating the code path at Level 1, and the documented facts on this page make Level 1 reachable by accident: the framework component that loads a tag manager is mounted in the root layout, the component that renders a banner is independent of it, and neither one knows about the other. A banner is evidence that somebody thought about the question, not evidence that anything changed.

Why would a developer not notice this themselves?

Because of the trap this axis names, the already-accepted illusion. The first time the preview loads, the banner appears, it gets clicked to move on, and from that moment the developer's browser profile carries the answer. Every later load, screenshot, demo and review happens after consent. Unlike an idle timer, which regenerates on its own if you walk away, a fresh profile does not come back. The condition is consumed once by the first person to look, which is why the protocol on this page specifies a new profile rather than a reload.

Methodology

Deletion and Data-Retention Posture: A Proposed Axis for What an AI Builder's Generated App Actually Removes (August 2026)

A proposed BuilderProof benchmark axis measuring what an AI app builder's generated application actually does when a record or an account is removed: declared referential semantics, identity deletion, retention exemptions, soft-delete coherence, third-party fan-out and residue disclosure. Six weighted signals, four posture levels, a reproducible protocol. Pre-registration only.

18 min read104