GitHub Issue
RFC: one gated actions core for GUI + MCP (ticket-minted policy, parse-once reports, frozen wire format)
## Problem
The same user-facing operation — "clean caches, maybe elevated, report freed bytes" — exists as **two parallel implementations in two processes**:
- **GUI path:** `CleanView`/`OptimizeView` → `guarded()` FDA checks + confirm dialogs + `pendingRun` closure parking → `CommandRunner` → `TaskReport.parse` → `OperationCenter.begin/detail/end`. `SoftwareView` hand-rolls the uninstall flow (confirm sheet, dry-run match preflight, `"y\n"` stdin answers) separately.
- **MCP path (separate process):** `ToolCatalog.callClean/callOptimize/callUninstall` re-spell the argv/stdin/timeouts, gate via `realActionAllowed()` (`confirm && Store.mcpActionsEnabled`), and return **raw unparsed text**. Purge/installer are preview-only with a hand-built redirect note.
Consequences:
- The argv tables, stdin scripts, and timeouts are spelled independently per surface and have already drifted (GUI uninstall 300 s vs MCP 600 s).
- Gating folklore lives in scattered ifs and comments: "root bypasses TCC for cache walks but not for Downloads/Desktop", "optimize's auth prompt *is* the consent", "uninstall needs the second opt-in switch". None of the GUI policy is unit-testable — it's trapped in SwiftUI closures.
- The uninstall **match preflight** (dry-run → pin mo's matched set → fail closed on divergence) exists twice and can half-regress.
- The stream parser runs only in the GUI; agents get text. The freed-bytes summary the GUI banner shows and the text MCP returns can disagree.
- The MCP wi...
View Raw Thread
SaaS Metrics