When juudd tells your assistant no, it writes the refusal down at that moment, with the exact sentence the assistant was given. A later assistant calls list_refusals and reads those sentences back, newest first. Nothing is summarised and nothing is reworded.
That is the decision. The rest of this post is why a refusal is the one record that cannot be rebuilt later, what an event carries, and the trade we took on purpose.
The one record you cannot reconstruct
Most of what this platform knows can be derived. Which sites exist, which versions are stored, which one is live: all of it can be worked out again from what is stored, on any day. When we sorted the platform's state by how costly it is to decide later, almost everything was cheap.
A refusal is the exception, because it leaves nothing behind. A deploy that the schema gate stops stores nothing. A site that belongs to another account stores nothing. The sentence the caller was told exists in one place, the reply, and the conversation that received it is gone when it ends. Every day without a record is a day permanently missing, and no later work can recover it.
What an event carries
One event is written for each act, at the moment it happens:
- when it happened
- the account it belongs to
- how the call arrived:
assistant,chat,panel,adminorsystem - the tool or panel operation
- the site, when it concerns one
- the outcome,
allowedorrefused, and for a refusal the reason
The split between allowed and refused is the entire point. A record of successes is what the platform already derives for free. How the call arrived is there because one person reaches this platform through more than one door, and "my assistant did that, I did not" needs an answer that nothing else can give.
An event never holds a credential. A refusal is written for a person, and it does not quote a connection string.
The sentence is the sentence
The reason is stored as the caller received it, character for character. The refusals about ownership are worded in one place in the code, so that several tools cannot each say "you do not own this" slightly differently to a model that acts on the difference. A history that paraphrased them would be a second wording, free to disagree with the first.
Some refusals arrive as thrown errors rather than returned sentences: the row-policy gate, the function checker, a bundle the rules will not accept. Those are the controls this product exists for, and a history that recorded a deploy only when it worked would be missing the day one of them fired. They are recorded with the message the caller read.
Reading it back
list_refusals lists what the account has been told no about, newest first. Each refusal is a line with the time, how the call arrived, the action and the site when there is one, and under it the sentence, untouched.
- With no argument it returns 20. Pass
limitto look further back. - When the page comes back exactly as full as it was asked for, it says there may be more. It never says "that is all", because a short page is not proof of an exhausted history.
- It lists refusals only. What succeeded is
list_deploys, which already derives it. - It is a read, annotated as one, and it records nothing of its own. A read of the refusals must not add to the refusals.
- It leaves out the identity claims an event holds. Explaining a refusal needs none of them.
An empty answer is a sentence: nothing has been refused on this account. That sentence can be trusted because the tool is offered only where refusals are recorded, so there is no deployment where an empty answer means nothing was written down.
The reason it exists is the customer who comes back in a later conversation. The assistant that was refused is gone, and its replacement has no idea the last three deploys were stopped for the same reason. Without this tool it guesses, or asks you to explain something the platform already wrote down word for word.
The trade we took on purpose
Writing an event never decides whether the act succeeds. If the write fails, the deploy still happens, the failure goes to the request log, and that line of history is lost. A stricter system would refuse the deploy rather than lose the record. That is right for a compliance system and wrong for this one, which does not claim to be one. A deploy failing because a record timed out is worse than a gap.
A service that did not answer is recorded the same way as a refusal. The record answers one question, did the caller get what they asked for and what were they told, and a refusal and an outage answer it identically. We considered a third outcome and did not take it: it would have made every earlier refused row ambiguous about which it meant.
What is not decided
- Nothing deletes events. A retention policy is a later decision, and a reversible one.
- Refusals from before the record existed are gone. The derived list of what exists is complete backwards; the record of what happened is complete forwards.
list_refusalstakes no site argument. Filtering a page by site would say "no refusals here" whenever that page happened to hold none, so a site filter will come later as an optional argument.
The rule, once
A refusal is an event: written when it happens, with the sentence the caller was given, never able to fail the act it describes, and readable by the next assistant through list_refusals. The tools and their limits are listed in the docs.