← RECORD 004 / WRITING & REFERENCE BUILDSBUILD 03
PERMISSIONS AT THE COMMAND LINE · HYPOTHETICAL PRODUCT · CODED, KEYBOARD-OPERABLE
Warrant CLI
A permissions tool that shows its reasoning in 80 columns of plain text. Every answer arrives with the chain that produced it, and nothing changes without --apply.
PROBLEM
Permission tools answer yes or no. The engineer needs to know which edge to change, and whether a revoke will actually undo anything.
APPROACH
Every claim is followed by its because lines: one fact per line, one hop per line, each with its author and expiry.
RESULT
Nine sessions that read the same in colour, with NO_COLOR set, and through a pipe. No prompt, no --yes, no --force.
A REFERENCE BUILD · A WORKED EXAMPLE, NOT CLIENT WORK. WARRANT, KESTREL AND EVERY NAME, ROLE AND PERMISSION HERE ARE INVENTED.
01 · THE OPERATOR
An on-call engineer, one terminal, and a production permission to change now.
The reader is an engineer on the platform rota, in a terminal they did not choose, on an incident where somebody needs access in the next four minutes. They are not a permissions specialist. They will use this tool perhaps eight times a month, which means they relearn it every time.
The second reader arrives six months later: an auditor with a transcript, asking what was granted, by whom, and on what basis. Both read the same output, because a permissions tool that prints one thing and records another has already lost.
WHAT A WRONG GRANT COSTS
A role with grant-option handed out during an incident propagates: the recipient can grant it onward, and a revoke does not follow those edges. The blast radius is discovered at the next access review, typically months later.
THE QUIET FAILURE
A revoke that reports success and changes nothing, because the access was inherited through a group and the operator revoked on the resource. The record now says the permission was removed. It wasn't.
02 · WHY THAT IS A DESIGN FAILURE
A yes/no answer hides the edge you have to change.
Ask a typical permissions tool whether someone can write to a resource and it says yes. That answer is useless for the only thing the engineer needs to do next, which is change it. Inherited through a group, granted directly, carried by a role, denied elsewhere but not here: all four render as yes, and each needs a different command to undo.
The confirmation prompt makes it worse. It puts the consequences in the one place people have trained themselves to dismiss, and it doesn't exist in CI, so the highest-risk path is the one with no friction at all. Within a fortnight somebody adds --yes and the prompt is decoration.
YES HIDES THE PATH
Direct and inherited access look identical, so the obvious revoke is often the wrong one and reports success anyway.
UNDO IS ASSUMED TOTAL
Open sessions, live tokens, onward grants and exported data all survive a revoke. Nothing in the output says so.
COLOUR CARRIES THE STATE
Red for deny, green for allow, and nothing at all in a CI log, a pipe, or a screen reader.
The claim this build tests: if every answer arrives with the chain that produced it, and every change states what it cannot undo, the engineer can act correctly the first time without knowing the permission model.
03 · THE CORE INSIGHT
One fact per line, one hop per line, and the mark in column one.
1 · THE ANSWER, THEN THE CHAIN
Line one is the answer and how it is held: direct, inherited, expired, denied. Every following because line is one hop with its own author, date and expiry.
2 · PLAN LENGTH TRACKS RISK
A staging read grant is four lines. The same command against production is twenty-two. Length is the signal, which is what makes the long plans get read.
3 · MEANING SURVIVES PLAIN TEXT
A mark in column one, a word beside it saying the same thing, and at most four of the eight base colours. Remove colour and the answer is unchanged.
The rule that follows: nothing changes without --apply, and every apply prints what it cannot undo. Above one principal, or on any prod-* environment, the plan digest must be passed back with --expect, so the plan that was read and the plan that ran can be shown to be the same one.
04 · THE SESSIONS
Nine sessions at 80 columns, in three render modes.
Every session is the real output, wrapped at 80 columns. Switch the render mode to see what a colourless terminal and a pipe with no TTY get: the marks and the words carry the meaning in all three. The last session is the prompting variant that lost.
$ warrant explain priya.raman write on pg:prod-eu/payments
ANSWER yes, write is in effect ^3 inherited, 3 hops
^3 priya.raman has write on pg:prod-eu/payments/*
because priya.raman is a member of group:eng-payments
added 2026-03-02 by n.falk · membership has no expiry
because group:eng-payments holds role:payments-writer
granted 2026-01-14 by s.adeyemi · reason "team charter"
because role:payments-writer allows write on this resource
role last edited 2026-05-30 by s.adeyemi · 8 permissions
also on record, not in effect now:
x direct grant of write, expired 6d ago (2026-07-21)
granted 2026-04-21 by m.dahl · reason "KES-4417"
! DENY of delete, rule r-2208, active (not write)
written 2026-02-19 by s.adeyemi
3 hops · 1 group · 1 role · 41 objects matched
verified against 2 providers, both answered (12ms, 31ms)
Invented org, invented principals. Nothing here is a client system.
SESSION 4 · THE WHY
Three hops, one expired grant, one deny that does not apply here
NOTICED FIRST
The answer on line one, then that it is inherited rather than direct.
WHY THE DESIGN CAUSES THAT
The reasoning travels with the answer. Each because-line is one hop with its own author, date and expiry, so a four-hop chain is four lines rather than a diagram. The engineer reading this knows which edge to change: revoking on the resource would do nothing, and the group membership is the thing that grants write.
WHAT IT COSTS
A deep chain is tall. Five hops across two groups runs to about twenty lines, and any shorter form drops the authors, which is the part an auditor needs.
CHECK THIS
The DENY line is about delete, not write, and says so. A red mark next to an answer it does not contradict is how a tool teaches people to ignore red marks.
TRUE IN EVERY SESSION
- The mark is in column one, the word beside it says the same thing, and no state is carried by colour alone.
- Nothing changes without --apply. There is no prompt in this tool and no --yes.
- The environment sits inside the resource reference, so it is typed every time and lands in shell history.
- Every claim is followed by its because lines. One fact per line, one hop per line.
- The exit code is printed when it is not 0, and documented.
TRY IT
Switch NO_COLOR on any session: the four colours go, the marks, bold and words stay, and the answer is unchanged. Switch piped and all styling goes, which is what a CI log and a screen reader get. Turn on the ruler to check the 80-column edge.
DECISION
Four of the eight base colours, and never as the only carrier of a state. Terminal themes are unknowable: the same escape code is a different colour on every machine, and one of them is somebody's low-contrast solarized light.
DECISION
Wrap at 72 with a hard limit of 80, so output survives an 80-column terminal, a pasted incident channel message and a PDF audit export without reflowing into nonsense.
05 · WHAT WE TRIED FIRST
Three versions were faster to use. All three were dropped.
Each was judged against one test: after running it, can the operator say what changed and what a revoke would fail to undo? Session 9 in the demo above is the first of these, kept in the build as evidence.
REJECTED · A
The confirmation prompt
Fastest for the everyday case, surprises nobody, ships in a week. The prompt is answered before it is read by day two, --yes exists by week two, and CI has no prompt at all, so the dangerous path is the frictionless one. Its transcript cannot tell you what was granted.
REJECTED · B
A blast-radius score
One number, 0–100, coloured. Compact and instantly comparable, and it collapses four unlike questions (how many objects, who else, what the role carries, what survives a revoke) into a figure nobody can act on. A score of 74 does not tell you to use payments-writer instead.
REJECTED · C
The chain as a tree diagram
Box-drawing characters, the inheritance graph rendered properly. Beautiful at four hops in a wide terminal, unreadable at eighty columns, and it has nowhere to put the author and date of each edge, which is the part the auditor came for.
KEPT · D
Plan, apply, and a chain of one-line facts
Every invocation prints a plan and changes nothing. The apply is a re-type with --apply appended, and above one principal or on production it also needs the plan digest. The chain is flat text: one hop per line, each with its author, date and expiry.
06 · THE REFUSAL DECISION
No prompt, no --yes, and no --force anywhere in the tool.
A refusal has four parts in a fixed order: nothing happened, the rule and the person who wrote it, what would have happened, and what to do instead. The third part is what earns trust. Learning that the role would have reached twelve service accounts is more persuasive than the refusal, and it is the line that changes the next command.
The last option offered is to ask the person who wrote the rule, with the tool saying plainly that it will not do it. A policy with an override flag is a policy the tool has already decided is decoration.
Piped, it refuses instead of prompting. There is no TTY, so there is nothing to confirm, and a tool that hangs on a prompt in CI gets wrapped in yes | forever. It exits 6 and prints the exact command that produces the digest.
The machine-readable output carries the chain, not only the verdict. A JSON payload that returns write: true has recreated the original problem in a new format, so the schema is versioned and every consumer gets the hops, the authors and the expiries.
The cost, stated plainly: printing what would have happened means computing the full plan for something being refused, so a refusal costs as much as an apply and tells the caller what the role carries. That is information a hostile caller can enumerate. We think a permissions tool that keeps its reasoning secret from its operator is worse, but this is the decision most likely to be overruled in a real engagement.
07 · DECISION LEDGER
Every significant decision, with its price.
| # | DECISION | WHAT IT SOLVES | WHAT IT COSTS | REJECTED INSTEAD |
|---|---|---|---|---|
| 7.1 | Plan by default, apply by flag | Every invocation is safe to run, so the consequences are read before anything changes and the plan is the auditable artifact. | Two invocations for every change, and a plan step that can decay into a formality once the tool is familiar. | A confirmation prompt. Dismissed by day two, --yes by week two, and absent from CI entirely. |
| 7.2 | Plan length tracks blast radius | A four-line plan and a twenty-two-line plan mean different things before a word is read, which is what makes the long ones get read. | The dangerous case is the one that scrolls, and a hurried engineer may read only the first block. | A fixed-length summary, or a single 0-100 risk score. Uniform length teaches people that length carries no signal. |
| 7.3 | Chain as one-line facts | The operator learns which edge to change, and the auditor gets the author, date and expiry of every hop. | A five-hop chain is about twenty lines. There is no shorter form that keeps the authors. | A box-drawing tree. Elegant at four hops in a wide terminal, unreadable at 80 columns, and nowhere to print who granted what. |
| 7.4 | Undo statement on every apply | Names the four things a revoke cannot reach: open sessions, live tokens, onward grants, exported data. | Four lines of bad news on every change, including the ones where every count is zero. | Printing it only when non-empty. That teaches people it is rare, which is when they stop reading it. |
| 7.5 | Digest required above one principal | The plan that was read and the plan that ran can be shown to be the same one, which is what makes an apply in CI auditable. | An extra copy-paste on exactly the changes people are most rushed about. | Always requiring it. Punishing the staging read grant for the production admin grant trains people to script around it. |
| 7.6 | Refusals print what would have happened | The refusal becomes information rather than an obstacle, and the next command is usually the right one. | A refusal costs a full plan computation, and it discloses what a role carries to whoever asked. | A bare permission-denied. Cheap, secret, and it leaves the caller guessing at the model. |
| 7.7 | Previews are logged like applies | Six months later the record shows what was considered as well as what was done, and every line replays by id. | Roughly ten times the log volume, on 400-day retention. | Logging applies only. Smaller, and it erases the evidence that somebody looked and decided not to. |
| 7.8 | Four colours, never load-bearing | The mark in column one and the word beside it carry every state, so NO_COLOR, a pipe and a screen reader all get the same answer. | Every state needs a glyph and a word as well as a colour, which costs horizontal room in an 80-column budget. | A red/green severity palette. Two muddy browns to a protanope and nothing at all in a CI log. |
08 · HOW WE WOULD VALIDATE IT
Four measures, and the result that would prove us wrong.
Sixteen platform engineers who are not permissions specialists, each a 45-minute session against a sandbox org, working from incident tickets rather than instructions. Warrant and the prompting variant counterbalanced.
MEASURE 1 · RIGHT EDGE FIRST TIME
Does the first revoke they run actually remove the access
The inherited-access ticket is the one that matters. Proves us wrong: no better than the prompting variant. The whole chain-in-the-output argument rests on this one.
MEASURE 2 · UNDO BELIEF
After a revoke, what do they think is still true
Ask about open sessions, live tokens and exported data. Proves us wrong: they still believe a revoke is total. Then the undo block is being skipped and needs to move, not grow.
MEASURE 3 · PLAN READ DEPTH
On the 22-line production plan, is grant-option noticed
Interrupt before the apply and ask what the grant carries. Proves us wrong: most read only the first block. Then plan length is a cost with no benefit and the ordering is wrong.
MEASURE 4 · DEGRADED PARITY
Same tasks with NO_COLOR, and piped into a file
Run with a screen reader on one cell. Proves us wrong: any accuracy drop between colour and no-colour. That would mean colour is carrying a state after all.
One measure that is not a usability metric: how many previews are run per apply after a month of real use. If it approaches one, the plan step has become a formality and the tool is a prompt with extra typing.
09 · LIMITATIONS & STATUS
What this build does not settle.
DEEP CHAINS GET TALL
Five hops across two groups runs to about twenty lines. Any shorter form drops the authors and dates, which is the part the auditor came for, so there is no compression available that keeps the value.
BULK IS UNADDRESSED
Everything here is one principal and one resource. An offboarding touches forty grants at once, and forty plans is a different tool with a different reading model, not a design.
THE SCHEMA IS A BILL
A stable JSON schema is a public interface. Schema 1 has to be supported for years, and every new state needs an enum value old consumers tolerate. That cost lands on someone else's roadmap.
PREVIEW LOGS COST TEN TIMES
Most invocations are previews, and recording them makes the audit log roughly ten times larger. The storage argument for dropping them will be made every year, and one year it will win.
REFUSALS LEAK STRUCTURE
Printing what would have happened tells the caller what a role carries and how many accounts hold it. In a hostile-insider threat model this decision has to be revisited, and we do not have a version that keeps both properties.
NOTHING FIXES BAD ROLES
The tool reports the role it is given. A role bundling six permissions that should be three produces honest output about a bad model, and no amount of output design detects that.
STATUS · CODED, KEYBOARD-OPERABLE, INVENTED DATA · PUBLISHED BECAUSE THE ARGUMENT IS FINISHED
READ ALONGSIDE THIS