Role: Builder and product owner · Built with: spec-driven development in the Kiro agentic IDE · Users: 200+ data owners and strategy & ops partners
Confidentiality note. Written at the level of detail already public on my resume. No schema, field definitions, access rules or record contents appear here.
The 30-second version. Sellers see data for the territories and accounts they are assigned. The exceptions to that — the overrides and one-off extensions — were being managed in a shared spreadsheet that anyone could open and edit. I replaced it with Allowlist: a central application where everyone can read every record and submit or withdraw one, nobody outside admin can alter an existing record, and every action is written to a log.
1. The thing that was actually broken
Access starts from a baseline. A seller is assigned territories or accounts, and sees the data for those.
The baseline is not the problem. The exceptions are, and there are always exceptions:
- A permanent override — a manager delegates something above the seller’s normal scope, and they need the data that comes with it.
- A one-time need — a special initiative requires a wider view for as long as it runs.
Both are legitimate. Both have to be recorded somewhere. And that somewhere was a shared spreadsheet with edit access for everyone who needed to use it.
The risk is not that the spreadsheet was inaccurate. It was probably mostly accurate. The risk is that it was unaccountable: any row could be changed by anyone at any time, and afterwards there was no way to establish who had changed it, when, or what it had said before. A spreadsheet governing data access is an access-control system in which the controls are a social convention.
That is the failure mode worth naming, because it doesn’t look like a failure until it matters. Nothing is visibly wrong. There is simply no answer available to the question “why does this person have this access,” and no way to get one.
2. What replaced it
Allowlist is a central application for the same records, with a deliberately narrow set of things a non-admin can do:
- Read every record. Visibility is not the thing being restricted — everyone can see the whole picture, which is what makes the register useful.
- Submit a record, or withdraw one. Both through a form.
- Not alter an existing record. That capability sits with admin alone.
3. Why removing “edit” is the decision
Taking edit away from the people who use a tool most looks like a downgrade. It is the choice I would defend hardest.
An edit is a deletion and an insertion with the history thrown away. When a row can be changed in place, the previous state stops existing — and no log can recover what was never written down. Forcing a change to happen as withdraw, then submit means the same change now leaves two entries behind it, and the sequence stays reconstructable.
That converts the register from a statement of the present into a record of how the present came about. For a system governing who can see whose data, the second is the one that answers questions. “Who has this access” is useful. “Who granted it, when, and what did it replace” is the question that actually gets asked, and only the second design can answer it.
The narrowness also makes the remaining permissions safe to hand out widely. Because nobody can quietly change a row, read access can go to everyone without anyone needing to be trusted with the register’s integrity.
4. The form is a data-quality instrument
The other half of the design is that submissions go through a structured form rather than free-typed cells.
That reads like a user-experience decision and isn’t one. It is about the downstream: the data team has to process these records, and free text produces typos and names that don’t match anything. Reconciling those is real work, and it happens at the point of processing — where the person doing it doesn’t know what was meant.
A constrained form moves that cost to the point of entry, where the person filling it in does know. This is the general shape worth carrying: validation is cheapest where the intent still exists. Every step it is deferred, it gets more expensive and less accurate.
It matters more here than it would elsewhere because of who the users are. The 200+ people using Allowlist are data owners and strategy & ops partners — the people sellers bring these requests to, not the sellers themselves. The person filling in the form is transcribing somebody else’s request, which is exactly the situation in which free text goes wrong.
5. The log is the product
Every action on the application, and on the data access it governs, is recorded.
I would argue this is the actual deliverable and the register is the interface to it. The spreadsheet already held the records. What it could not do was tell you how they got there — and that is the entire difference between a list and a control.
An audit trail is also the thing that makes the system cheaper to operate rather than more expensive. Questions about historical access stop requiring an investigation and become a lookup.
6. Built spec-first
I built Allowlist end to end through spec-driven development in the Kiro agentic IDE — writing the specification, then having the agent build against it.
The discipline is the same one I would apply without an agent: the specification comes before the code, and the code is measured against it rather than the other way round. Writing a PRD before building is the identical habit at a different altitude. What the agentic workflow changes is the cost of being vague — an underspecified requirement produces a confidently wrong implementation immediately, rather than surfacing three weeks later in review. The feedback on specification quality gets much faster, and much less forgiving.