Shipmind Labs

A vendor declares two purposes and gets granted one of them. It does not run. Not partially. At all.

That rule is why we stopped treating a vendor list as configuration and started treating it as data that gets checked.

In consentcore, our open-source project, the declaration is validated before anything is allowed to read it. A purpose with no id, an id declared twice, a category outside the known set, a purpose missing its name or description: rejected. A vendor with no id or name, declared twice, declaring no purpose at all, or pointing at a purpose id that was never declared: rejected. What you get back is plain data in fresh arrays, and lookups by id throw on an unknown id instead of handing you undefined.

Where that validation sits matters more than the checks themselves. The failure lands where the declaration is written, not three layers down in a call site where someone wraps it in a null check and moves on.

Permission then reads through that structure rather than beside it. A purpose gets answered through its category. A vendor gets answered by mapping its purposes to categories and requiring every one of them to be allowed. All, not any. The permitted vendors come back in declaration order, so the list a reviewer reads top to bottom is the list that actually runs.

The hash took us longest to get right. The declaration hash sorts purposes and vendors by id, sorts each vendor's purposes and cookies, and writes an absent policy or transfers link as an empty string before the JSON is hashed. The same value serves as the notice hash. Move a line in the file and nobody gets re-asked. Add a vendor or a purpose and everybody does.

Without that normalisation a formatting diff turns into a consent prompt for every user, and a banner that reappears for no visible reason probably trains people to click accept faster than anything else.

The tag layer reads the same declaration. Registering a vendor tag resolves the vendor first, so an undeclared vendor is refused at registration instead of sitting in memory forever, waiting for a grant that can never arrive. A tag with no declaration fails loudly rather than defaulting to blocked and looking like a consent problem.

The design cost is real. All-or-nothing means a vendor that bundles analytics with personalisation is dark for anyone who wanted only one of them. We think that is the correct cost, because the alternative is a vendor deciding at runtime which half of itself to be.

If you have built this layer, you have had to pick one of two things: let a vendor run on a partial grant, or make it split into separate vendors.

Was this useful?

Building something similar?

or email hello@shipmindlabs.com