Why a consent event is not a cross-tab guarantee
Updated 9 September 2026: the earlier replacement failures still matter, but Onboard2Stay now uses the standard Site Kit consent route. One consented QA page view reached Analytics; after confirmed withdrawal, no later event appeared in the captured next navigation. The custom replacement remains deferred.
The question behind the work
The problem
We want to measure a few useful actions on Onboard2Stay without collecting before analytics consent or starting new measurement after observed withdrawal. A banner that remembers a choice is only one part of that path. Already-loaded code, queued callbacks and other tabs can hold older state. We tested whether the whole path could meet our stricter engineering contract, then tried to break each proposed correction.
01
What we tried
- On 6 September, we removed duplicate Google analytics injection and configured cookie-category blocking. We tested rejection, acceptance and withdrawal in logged-out browsers, then kept an already-loaded page open and sent a synthetic diagnostic event after withdrawal. This tested continued execution as well as fresh-page blocking.
- After that failed, we disabled the affected injection and tested restoration guards locally. A two-tab Chromium fixture used a shared lock and a local receiver model. We tested writers outside the lock and storage failures, inspected the installed CookieYes 3.5.5 frontend, and ran a disposable SharedWorker experiment. A consent generation was our revision number for distinguishing earlier permission from later permission.
- On 7 September, the Owner authorised a separate replacement architecture. We began isolated tests of first-party daily counters with a small event allowlist, excluding form contents, product identifiers and URLs. We challenged expired consent scopes, withdrawal acknowledgement and the link between an original form submission and its later response.
- We then used real local WordPress and Fluent Forms installations with synthetic submissions, followed by an isolated MariaDB/InnoDB receiver. Database tests exercised simultaneous requests, row locks, expiry, duplicate handling and restart. A conditional shared-identity model and a later real two-tab browser fixture tested repeated initialisation, cookie binding, delayed responses and missing browser state. These were separate proofs, not a production integration.
- Later on 7 September, we tested whether restoring a pre-withdrawal SQL snapshot could bring an old grant back. The same drill ran against a real isolated local database and a protected hosted WordPress database, using synthetic identities, disposable tables and temporary test keys. We then compared reuse of the old test key with a new-key, empty-state recovery.
- On 9 September, after the Owner explicitly requested the existing Search Console and Google Analytics setup, we reused those connections instead of releasing the custom receiver. We enabled Site Kit consent mode, installed and activated the official WP Consent API, retained CookieYes as the choice interface, enabled Site Kit Analytics insertion, disabled Site Kit conversion tracking and deactivated the old duplicate measurement snippet. Enhanced measurement was limited to page views, scrolls and outbound clicks.
02
What happened
- The original active-page test failed: after withdrawal removed analytics cookies, the synthetic probe still sent a Google collection request and recreated an analytics cookie. The earlier quick probe followed by reload had been insufficient. The subsequent containment checks observed no Google requests, analytics cookies or loader elements on the tested new or reloaded pages, even after acceptance. Historical reporting was retained and new custom events stayed inactive. Server settings could not unload code from tabs opened earlier.
- We corrected our own evidence claims. Restricted DOM observations did not prove cookie or runtime state. Installed-source inspection confirmed that cookieyes_consent_update exists; our earlier event-absence assumption was wrong. In the inspected implementation, native writes and the start of blocked-script restoration preceded that notification. We found no supported awaitable pre-write boundary in the surfaces examined. This was a limit of our proposed integration, not a vendor-defect finding.
- The earlier locked model failed when both storage writes failed and when a native-style cookie cycle bypassed its lock. The SharedWorker experiment then failed when a remote grant cleared a tab's pending local withdrawal, allowing that tab to initiate a new synthetic event. We stopped and rejected that candidate. The separately authorised replacement did not reopen it.
- The replacement's first identity test exposed a misleading acknowledgement. One tab retained an expired scope while another had obtained a new granted scope. Revoking the old scope returned success, yet the other scope could still count a new event. A shared lock and a pending-withdrawal fence corrected the reviewed cooperative-client schedules. Passing those local cases did not establish browser-wide identity continuity.
- A separate production check found Jetpack statistics requests despite stored refusal. We disabled only its statistics and WooCommerce analytics modules. The cached homepage still sent the old pixel, so the setting change alone was not accepted as containment. After refreshing generated page cache, the executor's complete browser captures showed no listed Jetpack or Google collector requests on four routes under stored refusal and on a fresh no-choice homepage. Independent HTML checks found no Jetpack collector references or Google loader tags on those four routes. This was a scoped correction, not a certificate that every analytics path was off.
- Our first form adapter mistook a delayed success from submission A for completion of newer submission B. B could later fail while the counter stayed incremented. We bound completion to the original AJAX request and added a measurement deadline. Review then caught a leftover marker that could bind the wrong attempt; removing it before the next attempt fixed the tested schedules. Independent regression tests preserved both failures and passed the corrections. Real local WordPress checks exercised the plugin's serialization and successful submissions; deliberately reordered transport outcomes were established in the executable model. Measurement expiry did not unlock the plugin's own stalled-submit interface.
- Real InnoDB tests proved ordering and atomic counting for one identified consent record. Review also found two expiry errors: a physically retained but logically expired result was still reported, and an expired daily bucket retained its old count. After correction, an expired result became unknown, an expired bucket restarted at one, and live increments preserved the existing deadline. The targeted tests and 14 transaction test groups passed independent review. Physical cleanup and backup recovery remained unproven at that stage.
- Those database results did not solve browser identity. Revoking record B could not revoke an earlier request bound to record A. With one retained shared secret, nine independently rerun bootstrap test groups established stable reuse of the same record under concurrent initialisation and retries. Different secrets still produced different identities. Losing both cookie and shared storage left the local model off without a success acknowledgement, while an already-sent old request could still count before its deadline. We kept that counterexample visible.
- The later real-browser adapter used actual storage, locks and HTTP transport, but independent review found another error under a controlled lock schedule. An action called while denied could wait behind a grant and then be treated as authorised. A choice queued during pending withdrawal had a related problem. The correction now records the original permission state before waiting and checks it again after locks and responses. Independent source review and executable tests reproduced the old failures, passed the correction and challenged four further cases, including duplicate use and missed storage notifications. The executor also retested two real browser tabs; that browser and database evidence was read rather than independently replayed. This was a bounded local acceptance, not release approval.
- The restore reproduced an unsafe result. A synthetic identity was granted, its SQL state was captured, and its permission was withdrawn. Restoring that earlier state with the same test key made the old grant valid again. Changing the test key rejected the old cookie, request token and signed admission. Emptying the disposable measurement state first required cleanup health; after that, a new identity still started denied and could not issue a measurement action. The drill removed its seven temporary tables. It did not restore a provider backup or the website filesystem, rotate the active key, or release the replacement to production. Independent review reran the local SQL drill and passed its assertions. The hosted run remained executor evidence, read rather than independently repeated.
- In a logged-out browser that already had historical site cookies, a refused load still fetched the Google tag library but produced no Analytics collection request in the captured interval. Accepting Analytics produced one page_view request for the existing GA4 stream with an HTTP 204 response. After reopening preferences and visibly confirming Reject All, the Analytics cookies were absent, wp_consent_statistics was deny and the following normal navigation to /kennis/ produced no collection request in the complete captured interval. Google Analytics Realtime then showed one active QA user and one page view. This was a bounded observation of the standard flow, not a clean-room, zero-Google-network, basic-mode, cross-tab or legal-compliance certification.
03
What we learned
Initial blocking, active-page withdrawal and later acceptance need separate tests. Removing a cookie or receiving a consent notification does not prove that queued work and every producer obey the same permission boundary.
An acknowledgement must name the authority it actually changed. A correct transaction for one consent record does not prove that every tab and in-flight request belongs to that record. Repeating initialisation safely cannot reconstruct browser continuity after all identifying state has disappeared.
Preserve the authority of the original action before it waits. A later grant must not rescue an action that began while denied. Likewise, a form-wide success notification cannot establish which submission succeeded; completion must stay attached to the originating request.
Configuration is not the delivered page. Cached output can keep a disabled producer active until the generated page is refreshed. Check the actual request path after the change and keep the claim limited to what was observed.
Logical expiry must govern reads and writes even before physical deletion runs. Durable row locking and atomic updates solve specific ordering problems; they do not prove browser continuity, cleanup, backup safety or the full release path.
Keep failed schedules as regression evidence and label the test environment. Real plugin observations, simulated callback order, actual database concurrency and production network captures answer different questions. A local pass does not authorise a broader claim.
Restoring website data can also restore old permission. Recovery therefore needs an explicit authorization reset: keep collection off, separate restored consent state from the new runtime, establish a new key and empty healthy measurement state, and require a fresh explicit choice. A successful content restore is not evidence that old grants are safe to reuse.
A standard integration can be the proportionate choice when it covers the current need, but it still needs one identified producer and browser tests of both acceptance and confirmed withdrawal. A loaded consent-mode tag, the absence of an event in one interval and a Realtime event each prove different things. Because the Google library loaded before consent in this setup, the result cannot be described as zero Google contact before a choice.
04 · Outcome
The decision
Keep the existing Search Console and GA4 connections live through Site Kit consent mode, the official WP Consent API and CookieYes. Site Kit remains the single Google Analytics producer; plugin conversion tracking and the old duplicate snippet stay off. The observed ordinary acceptance and confirmed-withdrawal path is accepted for this bounded setup. Do not release the custom receiver or replacement architecture: it remains deferred, the rejected SharedWorker candidate stays closed and the Cloud86 provider request remains unsent.
Where the evidence leads
What this means next
Monitor the bounded standard setup and repeat acceptance and withdrawal checks when the consent stack or Google insertion changes materially. Treat the Site Kit email-report failure and historical RSS sitemap failure as separate follow-ups. Reconsider the custom receiver only for a concrete need or new Owner direction; do not infer cross-tab safety, zero Google contact or legal compliance from this release.