Back to Ideas 7 min read

What We Count, and What We Refuse to Know

E
EkoHacks Team
·

A privacy policy usually describes intentions. This one now contains a table, and the table is a claim that can be checked: every system that holds anything about a visitor to this site, what it holds, where it runs, why, and for how long, with the sentence underneath that if a system is not in the table it holds nothing about you. Writing that sentence honestly meant finding out whether it was true, and it was not quite.

The reframe

A policy that lists systems is checkable. A policy that says we take your privacy seriously is not, and neither is one that says we collect only what you give us, because the reader cannot see what that is. A table of systems can be compared against the code by anyone who can read both, and it can be compared against itself by the people who wrote it, which is what we did. The inventory is not a legal formality. It is a test of whether we know what our own site does.

What the inventory found

We went through the code that sends anything anywhere and wrote down where it went. Three things did not match the policy.

The policy described a workshop registration form collecting a name, a phone number, an experience level and a message. No page on the site rendered that form. Its component was still in the repository, along with a second contact form that called an endpoint deleted in June, and a hidden copy of both in the file our host uses to detect forms. None of it was reachable by a visitor, so no data was being collected, but the policy was describing a door that had been bricked up. Both components and the detection stubs are gone, and the policy now describes what replaced them: a seat on a course is booked on our dojo, where the checkout is provided by a payment company acting as merchant of record. That company was nowhere in the policy either, though it has taken payments since August. It is now in section 2, section 6, section 8 and the table.

The newsletter form wrote a subscriber's address to two places: the mailing service the policy named, and our host's form storage, which it did not. The second copy was a fallback somebody added and nobody documented. One datum, one system: the copy is gone, and the form now writes to the mailing service only.

And the contact form that visitors do use was fine, which we confirmed by reading the component the page actually renders rather than the one with the same name beside it.

What we count

Post seven added page views. This post adds three counts, and the word is chosen carefully. When someone subscribes to the newsletter, sends a contact message, or opens a seat booking, the site tells the counter that it happened:

export function track(event: "Newsletter signup" | "Contact sent" | "Seat booking opened") {
  if (typeof window === "undefined") return;
  const plausible = (window as unknown as { plausible?: (name: string) => void }).plausible;
  plausible?.(event);
}

The event is a name and nothing else. It does not carry the email address, the message, the seat, or any property that could be joined back to a person. It answers one question, whether the site's forms are used at all, and the answer is a number. The three events are named in the policy, in the sentence that says what they do not carry, and they appear as the fifth row of the table. The goals that display them were created by hand in the counter's dashboard, because we decided not to hold the key that can create them from code; that key can also delete the site, and we do not need it.

The table

WhatHeld byWhereWhyFor how long
Contact messagesNetlify FormsUnited StatesTo reply to youUp to 12 months after our last message
Newsletter subscriptionsLoopsUnited StatesTo send the newsletter you asked forUntil you unsubscribe, then 30 days
Seat bookingsPaddle, and our Paddle accountUnited KingdomTo deliver what you bought, and accounting lawSeven years
Page viewsPlausibleEuropean UnionTo see which pages are read and from whereDaily hash deleted within 24 hours; totals kept
Counts of the three eventsPlausibleEuropean UnionTo see whether the forms are usedTotals kept; nothing about you in them
Email you send usProton MailSwitzerlandTo reply to youUntil the conversation is over

Six rows. Two of them are in the United States, which the policy has always said and now says per row. One is the payment company, which holds card details we never see. The two that came in with this series are in the European Union and hold no personal data at all.

What we refuse to know

The table is also a list of absences. No cookies. No advertising identifiers. No session recordings. No data brokers. No cross site anything. No analytics on who a reader is, only that a page was read. We wrote the absences into the policy as a sentence under the table, because a reader should not have to infer what is missing from what is present.

While we were in the file we found that every bold label in the policy and in the terms, thirty four of them, had been rendering as literal asterisks since they were written, because the closing marker had a space inside it. That is fixed too. It has nothing to do with privacy and everything to do with whether anyone could take the page seriously.

The number for today: six systems in the table, two forms removed from the code because they were not on the site, one duplicate copy of every subscriber's address that no longer exists.

E

Written by

EkoHacks Team

More from Ideas

·6 min read

What the Nullable Gave Back

One file, seven behaviours held fixed, the database swapped for a Nullable: about 180 times less time inside the tests, and coverage flat to two decimals.

E
EkoHacks Team
·6 min read

Twenty Six More Tests, Four Fewer Behaviours

Removing the mocks grew the suite from 44 tests to 70 and quietly deleted four behaviours, every one of them a failure path. Test count is not coverage.

E
EkoHacks Team
·6 min read

The Best Coverage Number in the Room

Same commit, same spec, same test count. The mocked suite ran 5.6 times faster, covered 3.5 fewer points of real code, and posted the best branch coverage.

E
EkoHacks Team