The problem: “who asked” is a separate question
Parmana’s other evidence answers questions about business actions. A Trust Record says this refund was approved and released. A Refusal Record says this refund was refused by this policy. Neither answers “who tried to talk to the API at all”. A leaked key turned away at the door never reaches a policy, so it leaves neither record. If the door keeps no record of its own, the weekend is invisible.The idea: the door keeps a signed, chained log
Every request carries one API key as a bearer token. The key says which system is calling. Every outcome at that layer is written down as an audit event, signed with the same key that signs everything else Parmana produces, and linked to that caller’s previous event, so a missing row shows up as a gap.How Parmana does it
Keys. The server never stores a raw key, only its SHA-256 hash, and compares in constant time. Records name the caller by its caller id, never by the key. Each key’s entry decides what it may do: which actions it may ask for (none, if the list is left out), which principals it may act for (only itself, by default), which tenants, and whether it belongs to a verified person. Only keys held by a person may use the governance routes. Events. These outcomes are recorded:- A caller authenticated.
- A key was rejected.
- An action was allowed for that key, or denied.
- A key tried to act for a principal it may not act for.
- A key not held by a person hit a governance route. This one is flagged.
- A request was structurally invalid: a malformed or oversized body, a malformed transaction id, an invalid transaction, or a duplicate.
What it does not do
- A key is not an approval. A valid key proves which system is calling. It does not authorize anything. Every agent action, reads included, still needs a signed human approval and a policy that allows it, checked after the key.
- It does not catch every deletion. Deleting a caller’s entire history at once leaves nothing to show a gap. Reordering or deleting across different callers’ chains is not detected either. These are stated limits.
- Some events are not chained. An unknown key and a malformed body have no caller to chain to. Those events are signed but not linked. The malformed-body event is also written on a best-effort basis: if that write fails, the 400 still goes out.
- Rate-limited requests are not in it. Over the limit, a request is refused before anything is recorded.
- Only the production sink signs. In test mode the audit sink is in memory, unsigned and unchained. Rows written before signing shipped have no signature.
- Authentication can be switched off. For local development and tutorials, a setting turns caller authentication off. Policy and approval checks still run, but nobody knows who asked. The server warns at every start and reports it on its readiness check, and the public sandbox refuses to start with it.
- Key management is manual. There is no API to create or rotate keys. The operator issues them and redeploys.
- It does not stop someone with database access and the signing key. The chain makes tampering detectable. It does not make it impossible for someone who holds both.
- In the public sandbox, chains start partway. The sandbox deletes visitor data on a schedule, so the oldest surviving event points at a deleted one. In production nothing deletes these rows.