c36d41b9d5
Make the grant system's wider scopes real and add a revoke producer. Before: grants were loaded per-session (the gate folds only the running session's own stream), so GrantScope.PROJECT — though declared and matched in the engine — was dead in practice, and there was no GLOBAL scope or any way to revoke a grant (ApprovalGrantExpiredEvent had no producer). - GrantScope: add GLOBAL(toolName); make PROJECT tool-bound. Every operator-creatable scope is now tool-bound so a grant can never be a blanket "approve everything" (that's YOLO mode). - DefaultApprovalEngine.scopeMatches: GLOBAL matches the bound tool in any context; PROJECT matches when the session's projectId AND tool match. - Cross-session ledger: PROJECT/GLOBAL grants are appended to a reserved GRANT_LEDGER_SESSION_ID stream instead of a session's. The approval gate now unions the ledger's grants with the session's, and derives projectId from the bound workspace root (ProjectIdentity.of) so PROJECT grants match later sessions on the same repo. SESSION/STAGE grants still live in (and die with) their session. - Revoke: ClientMessage.RevokeGrant appends ApprovalGrantExpiredEvent to the ledger (reducer already drops it); ListGrants/GrantList expose the active standing grants for the TUI viewer. - Drop the server-side T2 grant ceiling per operator request: a grant may now authorize any tier (incl. destructive T3/T4). Tool-binding is retained as the remaining guard. Backend only; the TUI scope picker + grants viewer follow. core:events, core:approvals, core:kernel, apps:server compile; approvals/events/kernel/ server suites green; new GrantScopeMatchingTest (5) covers GLOBAL/PROJECT match, project isolation, and the no-cap T4 path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>