Where classification actually earns its place
Sessions 1 to 3 built the frame: three counters that trade against each other, roles against attributes, and least privilege that people can work with instead of route around. All three assume somebody has said which columns matter and how much. That statement is classification, and it is only worth writing if a policy reads it. Tonight it gets written in the form a policy can read, over saltbox.customers at Saltbox, a consumer lending startup: ten columns from a plan name to a national ID, three regions, five jobs.
★ What you walk out with tonight. Four classes with a default treatment attached to each, so the label decides the behaviour instead of describing it. The exception rule in its full form, which is a named role plus a written reason plus a date. And the finding most classification exercises miss entirely: the free-text column, which is almost always filed by what it usually holds rather than by what it can hold.
LiveA label that no policy reads is decoration3 min▶
The test for a classification scheme is not whether the tiers are sensible. It is whether a person reading a column name can say, without asking anybody, what happens to that column by default and who is allowed the exception. If the answer needs a meeting, the scheme is a vocabulary rather than a control.
That is why each class here carries a default treatment. "Contact PII" is not a description of name, email and phone; it is a commitment that those columns come back masked unless a named role asks, and that the named role is written down where the policy lives.
Saltbox ran a classification exercise a year before anybody wrote a masking policy. Every column got a tag, the tags went into a governance tool, and the warehouse never referred to them once. When masking finally arrived, the team re-did the whole exercise from scratch, because nobody trusted tags that had never had a consequence attached to them.
Self-studyThe four words this session turns on4 min▶
- Class - a group of columns that share a default treatment. The design is written against the class, not the column, because a grid of jobs against columns stops being readable at around ten columns.
- Default treatment - what happens to a column of this class when nobody has said otherwise. Masked, or readable, and never "it depends".
- Exception role - the one named role allowed to read a masked class in clear, with a stated reason and a review date. A class may have exactly one, or none.
- Masked - the column comes back transformed rather than removed, so the row stays usable and the value stays unreadable. It is the third value, and it is what lets a job proceed honestly instead of being blocked.
One thing that is deliberately not on that list is a sensitivity score out of five. Scores invite averaging, averaging invites a threshold, and a threshold is where a column with a national ID in it ends up on the same side of a line as a postcode.
Three or four classes, never seven
10 min liveThree or four is not a rule of taste, it is the range where people file columns consistently. Past that, two reasonable colleagues put the same column in different classes, two policies end up disagreeing about it, and the disagreement is discovered by an auditor rather than by you.
LiveThe four classes, and the treatment each one implies4 min▶
Four classes cover a customer table without anybody having to think hard, and thinking hard is exactly what a classification scheme must not require at the moment somebody adds a column.
- Non-sensitive - region, plan, product. Readable by anybody with warehouse access. This class exists so the other three stay small; if it is empty, the scheme is too suspicious to be used.
- Contact PII - name, email, phone. Masked by default with one named exception role, because these are the columns whose entire purpose is reaching a specific human.
- Highly sensitive - national IDs, full dates of birth, and anything that unlocks an account somewhere else. That last phrase is the working definition and it is more useful than any list, because it catches the recovery answer, the partial card number and the passport scan reference nobody thought about.
- Financial - revenue, balances, limits. Not personal, still restricted, and reliably the class where the actual argument happens, because the people who want it are senior and their reason is genuinely good.
Notice what the classes do not encode: who is allowed to see them. That comes from the job, and keeping the two apart is what stops the scheme being rewritten every time somebody joins.
LiveWhat goes wrong at seven classes3 min▶
A seven-tier scheme fails in a specific and boring way, and it is worth naming because the failure is never attributed to the tier count.
- Columns get misfiled. With four classes, the boundaries are obvious enough that two people agree. With seven, "sensitive personal" and "restricted personal" are both defensible for a date of birth, so the column lands wherever the person filing it happened to be that afternoon.
- Two policies end up disagreeing. Once a column sits in one class in the governance tool and another in the warehouse tag, both are true and both are enforced somewhere. The disagreement is invisible until an audit reads both.
- Nobody can hold it in their head. The scheme has to be usable by an engineer adding a column at five o'clock. Four classes fit in a sentence; seven need a page, and the page is not open.
The recoverable version of a seven-tier scheme is usually four classes with three of them empty. Merging the empty ones costs nothing and removes the ambiguity that was doing the damage.
LiveThe free-text rule3 min▶
A column is classified by what it can contain, not by what it usually contains. That single sentence catches the biggest classification miss in most estates, and it applies to every free-text field: support notes, comments, internal descriptions, the "other" box on a form.
Look at what a support notes column accumulates over a year of ordinary use. Agents paste in whatever the conversation required. That means card numbers read out over the phone, medical details offered as the reason for a missed payment, and every so often a password the customer volunteered before anybody could stop them. Nobody intended any of it, and all of it is now sitting in a column filed as operational text.
Sample before you argue. Take 200 random rows of your largest free-text column and read them. The conversation about which class it belongs in ends in about four minutes, and it ends without anybody having to be persuaded.
Self-studyThe two boundaries that actually get argued4 min▶
Two columns cause most of the debate in a first classification pass, and both are worth deciding in advance so the meeting does not stall on them.
- Date of birth. Argued as contact PII because it feels mild, and it belongs in highly sensitive because it is a standard identity verification answer at banks, telecoms and utilities. A birth date plus a name is often enough to pass a phone check somewhere else, which is precisely the "unlocks another account" test.
- The customer identifier. Argued as non-sensitive because it is a meaningless integer, and it is not meaningless once it can be joined back to a table that carries a name. Identifiers are usually the column that needs the third value rather than either binary answer.
A third one shows up later and rarely in the first pass: derived columns. A risk score computed from a national ID does not carry the ID, but it can leak information about it, and a churn probability sitting next to a name is a statement about a person. Derived columns inherit the class of their most sensitive input until somebody demonstrates otherwise.
The exception is the whole decision
8 min liveMasking a class is the easy half and it is not where the risk lives. Everything interesting is in the exception: which single role reads the class in clear, for what stated reason, approved by whom, reviewed when. A masked class with a vague exception is weaker than an unmasked one, because it looks decided.
LiveMasked by default, with one named exception4 min▶
The Saltbox design masks contact PII for everybody and names exactly one exception: the support agent role, because support has to reach the actual human on the other end of a ticket. That reason is written next to the exception, in one sentence, and it is deliberately specific enough to be falsified. If support ever stops making outbound contact, the sentence is no longer true and the exception goes.
National ID has no exception role at all, and that absence is the point rather than a gap somebody has not got to yet. Nobody at Saltbox has a job that requires reading a national ID out of the warehouse, so there is no role to name. An empty exception list survives a review, because there is nothing to justify. A list with one vague entry does not.
- One role, not a group of roles. Two exception roles on one class is the beginning of "and also", and "and also" is how a masked class quietly becomes readable.
- A reason a stranger can evaluate. "Operational necessity" is not a reason. "Has to phone the customer back" is, because somebody can check whether it is still true.
- Absence beats vagueness. The strongest line in a design is a class with no exception at all, and it only stays strong if nobody adds a hedge to it.
LiveWho approves an exception, and how it comes back3 min▶
An exception needs three things attached to it before it is deployed, and the third is the one that is always missing.
| Attached to the exception | Why it is there |
|---|---|
| A named role, never a person | People join and leave the role; the decision is about the job, so it survives them |
| A one-sentence reason | The only thing a reviewer can evaluate later. It has to be checkable, not respectable |
| An approver, by name | Whoever owns the table. Not the platform engineer who has the ability, and not the requester's manager alone |
| A review date | Without one, the exception outlives the reason and nobody notices, because nothing prompts anybody to look |
The review itself is short if the reason was written well: read the sentence, ask whether it is still true, keep or remove. A review that requires reconstructing why an exception exists has already failed, and it fails at the moment the exception is granted rather than at the review.
LiveThe third value, and why a binary grid cannot work4 min▶
Some jobs need a column to be present without needing it to be readable. That is not a compromise, it is the honest requirement, and a grid offering only allowed or denied forces a wrong answer every time it comes up.
The churn model at Saltbox is the clean example. It needs a stable customer key so rows join across tables and across months. It does not need to know whose key it is. Allowed hands over a real identifier the work never asked for. Denied removes the join and the model with it. The third value, masked, keeps the rows joinable and the people unidentifiable, and it is the only answer that is true to the requirement.
Find these before the meeting. Any column used in a join, a group-by or a segment definition is a candidate for shape-without-value. In most customer tables it is the identifier, the postcode and the birth date, and all three usually arrive at the meeting classified as a binary fight.
Self-studyWhat masking is worth, counted4 min▶
Saltbox scored every combination of five jobs, ten columns and three regions against the written design: 150 requests, of which 41 legitimately need a value in clear, 3 need it masked and 106 have no business being answered. Masking gets added in two steps.
| Policies on | Leaks | Over-blocks | Standing privileges |
|---|---|---|---|
| One shared analyst role | 109 | 0 | 30 |
| + mask national_id for everybody | 94 | 0 | 25 |
| + mask email and phone, except support | 70 | 0 | 17 |
| + split the shared role five ways | 22 | 0 | 1 |
Read that honestly and it is uncomfortable. Two rounds of masking, which is the work everybody associates with data protection, take leaks from 109 to 94 to 70. The next row, splitting one shared role into five, takes 70 down to 22 and standing privileges from 17 to 1. The remaining 70 are a role modelling problem rather than a masking one.
The lesson is not that masking is optional. It is that masking is the visible control and role modelling is the effective one, so a programme that spends its whole first year on classification and masking will report real progress and still be holding most of its exposure.
Classify one real table, exceptions included
★ 18 min · everyone buildsOne table, every column, no abstentions. If you have nothing to hand, use saltbox.customers: customer_id, name, email, phone, dob, national_id, region, plan, mrr, support_notes, across EU, UK and APAC.
Put every column in one of the four classes. Non-sensitive, contact PII, highly sensitive, financial. Every column, including the boring ones, because an unclassified column defaults to readable and that default was never approved by anybody. Work fast; the columns that take longest are the interesting ones and they get their own step.
For each masked class, name the exception role and write the reason. One role, one sentence, in the present tense, specific enough that somebody could tell you it is no longer true. If no job needs the class in clear, write none deliberately and say so out loud, because an empty list written on purpose reads completely differently from an empty list nobody filled in.
Find your free-text columns and reclassify them. Notes, comments, descriptions, the "other" box, the internal reason field. Reclassify each by what it can contain. If somebody objects, sample 200 rows in the room; the objection usually withdraws itself.
Mark every column where somebody needs the shape but not the value. Walk the joins and the group-bys. Each one you find is a place where a binary grid would have forced you into either handing over an identifier or breaking the work, and both of those decisions are currently being made somewhere in your estate by default.
Write an approver and a review date next to each exception. A name, not a team, and a real date within the next two quarters. An exception with no date attached is a permanent grant that has been described as temporary.
Keep the classification where the policies live. A class list in a governance tool that the warehouse never reads is the exercise Saltbox had to do twice. Next to the policy definitions it becomes something a change can be checked against, and something an auditor can be handed without a rehearsal.
Before session 5
30 min- Finish the classification for one real table and count how many classes you actually used. If it is more than four, find the two you can merge without losing a decision.
- Write the exception register for that table: class, exception role, reason, approver, review date. One row per masked class, including the rows where the exception is none.
- Sample 200 rows of your largest free-text column and note the three most surprising things in it. Bring the count, not the contents.
- List the columns that need shape without value, and for each one say what currently happens instead. In most estates the honest answer is that the real identifier is handed over.
- Ask whoever owns your most sensitive table whether they know they are the approver for its exceptions. An approver who has not been told is a signature waiting to be a surprise.
Three questions
A support notes column mostly holds shift handover text. Over a year it has also collected a card number, a medical detail and a password a customer volunteered. Which class does it belong in?
Free text accumulates whatever the conversation required. Classifying it by typical content guarantees the exceptions are unprotected, and the exceptions are the whole reason the column matters.
The national_id class is masked and has no exception role. Someone proposes adding one for a team with no stated use yet. What does the empty list currently mean?
Absence beats vagueness. An empty exception list survives a review because there is nothing to justify, and anything added to it has to arrive with a reason somebody outside the team can evaluate.
Starting from one shared analyst role at 109 leaks, what do the two masking steps achieve?
109 to 94 to 70. The next step, splitting the shared role five ways, is what takes 70 to 22 and standing privileges from 17 to 1. Masking is the visible control; role modelling is the effective one.