ADR 0014: Approvals have four scopes; project and always persist under ~/.wisp
Date: 2026-09-19. Status: accepted. Amends ADR 0011.
Context
Asking on every risky command makes routine delegation (a build-test loop, a git workflow) tedious and
multiplies exposure to the client’s dialog problems. The MCP dialog had lost “approve for this session”
when it became fieldless. The owner decided that wisp may keep approvals after the process stops, in a
file under ~/.wisp.
Decision
- An approval carries a scope:
once(amended 2026-09-19: the rest of the current turn, so one answer covers a prompt’s whole tool loop),session(this process),project(this exact command in this exact directory), oralways(this exact command anywhere). Chat answersy,s,p,a, orn; the MCP dialog offers the same four in a picker with Accept and Decline;--yesandAutoApprovermean once. projectandalwaysare written to~/.wisp/approvals.json(user-only, atomic writes) byApprovalStore, with a random id, the level at grant, the source entry point, and an expiry (approval.persistDays, default 30). Expired entries are dropped on load.- Exact matching only. No patterns or prefixes: a prefix rule such as “anything starting with git” is exactly the standing permission we do not want.
- Dangerous commands are never persisted. A
projectoralwaysanswer for a dangerous verdict is downgraded tosessionand audited as such; dangerous commands ask again in every process. - A persisted approval decides only whether to ask. Deny patterns, the sandbox, and the classifier still
run on every use, and every use is audited as
approval.decidedwith decisioncached-projectorcached-alwaysand the approval id. wisp approvalslists standing approvals;revoke <id>andclearremove them.
Consequences
- Standing permissions are inspectable and bounded; the audit log links each use to its grant.
- Exact matching means slightly different commands still ask; that is the intended trade.
- The store is per user, not per project, so
projectentries carry their directory explicitly. - The picker returns to the MCP dialog; if a client cannot render it, Accept still means once.