diff --git a/plan/policy.md b/plan/policy.md index f325ea60..162525e8 100644 --- a/plan/policy.md +++ b/plan/policy.md @@ -408,6 +408,29 @@ first: expect them is open. - **Whether deletes are evaluated**, per the item above. +## Consent is a denial, evaluated at authorize + +An agent has nobody at a consent screen, so the question at authorize is not +whether somebody approved an application — it is whether this one is denied. +That is this epic's own model applied one step earlier, and the machinery is +already shaped for it: the grant is a subject kind of its own, and denying an +application by its client identifier applies to a grant request as much as to +a write. + +- [ ] **Permit-unless-denied, but only when a tree is loaded.** "No denial" is + a decision; "policy could not be read" is not. The consent seam's own + invariant — approval must never happen merely because there was nobody + to say no — stays true if absence of a *denial* answers and absence of a + *source* refuses. The last-good machinery is what tells the two apart. +- [ ] **Write the lexicon document.** The record shape is already read: polled + from the operator's own repository, with the predicate behind a blob + reference so the record stays small. What is missing is the JSON, and it + has to express denials or it contradicts this epic on its first line. + **A new record type needs a human's approval.** +- [ ] **Say that the evaluator runs in both places.** Denials are checked at + the write and at authorize, and an epic that names only one of them will + drift from the code that does both. + ## Done - The `compile` step on `Evaluator` (three outcomes, a `CompiledId` the