Shipmind Labs

A tag that names a vendor belongs on the page only while every purpose it was declared for is granted. One of two is not a partial yes. It is a no.

Most consent layers miss this the same way. They treat a vendor as one switch, flip it when any related purpose is accepted, and leave the page in a state nobody consented to.

In our open-source project consentcore we modelled this as two kinds of tag rather than one tag with two fields. A category tag carries a purpose. A vendor tag carries a vendor id. The registry tells them apart by asking whether the tag has a vendor at all, and from there the two follow different rules the whole way through.

The gate routes on that split as well. A category tag gets checked against the granted purposes. A vendor tag gets checked against the vendor's own allowance, which is the conjunction of everything the declaration lists for it. So a vendor declared on two purposes stays held while only one is granted, and on every consent update the tag is revoked the moment any single one of its purposes is refused or withdrawn. Revocation is cheap. Being wrong on the page is not.

The less obvious decision was where to validate the declaration. Registration runs its checks in order: a repeated id is a duplicate error first, then a vendor tag has to resolve against the declaration. If no declaration was supplied, that is an invalid-declaration error. If the declaration does not list that vendor, registration is refused outright. The tag never enters the registry.

That ordering matters because the tempting alternative is to accept the tag and hold it forever. It looks safe. It is probably the worst outcome available: a silent, permanent pending state that a developer reads as "consent hasn't arrived yet" when the truth is the vendor was never declared. A typo in a vendor id should fail loudly at wiring time instead of waiting quietly for a grant that can never come.

Only after those checks does the tag enter the registry, and if the gate already permits it, it is released immediately and synchronously (no deferred tick, no race with the first page view).

The same split runs out to the edges, which is where we judged whether the model held. When a tag mounts into the DOM, the host writes a vendor attribute for a vendor tag and a purpose attribute otherwise, not both. The event we record carries either the vendor or the purpose. So the history can name the exact answer each tag was gated on.

That last part is the real deliverable. When someone asks why a particular script ran, "consent was given" does not answer it. "This tag was gated on this vendor, and here is the grant" does.

If you maintain a consent layer, it is worth checking whether a vendor spanning several purposes fires on the first grant or only on all of them.

Was this useful?

Building something similar?

or email hello@shipmindlabs.com