# Box Approvals over HTTPS (No SSH) > **Box is the main surface.** All operator work goes through Box (box.muse-dev.online). The web UI, `box` CLI, and agents share the same API endpoints. No UI-only powers. **Date:** 2026-10-06 **Status:** implemented on bl (`exec-constrained.py` + `box-relay.sh`; `box-ctl.py` verbs pre-existed, plus fast node validation and `quality-validate` branches; `approvals.py` untouched) **Scope:** approval visibility (check) + governed decisions (deny, auto, one-shot allow). Persistent/forced allow (`--always`/`--force`) stays SSH-only. ## 1. Why Agents blocked on browser approvals needed SSH to see fleet approval state, deny a bad prompt, auto-resolve trusted prompts, or allow a known-good one. All of this now rides the agent HTTPS path (`https://exec.muse-dev.online/exec`, signature or Bearer [REDACTED], named-op allowlist, audit log). ## 2. New ops | Op | Args | Backend | Access | |---|---|---|---| | `approval.check` | `{node?}` (default fleet) | `box-ctl.py approval-check` | read-only, in `DEFAULT_PERMS` | | `approval.deny` | `{node!, message!, allow_main_chat?}` | `box-ctl.py approval-deny` | known-identities-only | | `approval.auto` | `{node?}` (default fleet) | `box-ctl.py approval-auto` | known-identities-only | | `approval.allow` | `{node!, message!, allow_main_chat?}` | `box-ctl.py approval-allow` (one-shot) | known-identities-only | New `box-relay.sh` client commands: ```bash box approvals check [node] box approvals allow --message [--allow-main-chat] box approvals deny --message [--allow-main-chat] box approvals auto [node] ``` Raw op calls (signature auth, no token): ```bash exec-sign.sh approval.check '{}' exec-sign.sh approval.check '{"node": "646"}' exec-sign.sh approval.allow '{"node": "646", "message": "trusted deploy script"}' exec-sign.sh approval.auto '{"node": "opm"}' ``` ## 3. What the decisions do - `approval.allow` clicks Allow **once** on the node's active prompt. There is deliberately no remote `--always` (persistent site allow) or `--force`. - `approval.deny` clicks Deny on the node's active prompt. - `approval.auto` scans (fleet or one node) and allows only TRUSTED non-key prompts. Key/passkey approvals are never auto-approved; they need an explicit allow/deny, which notifies the waiting agent. - All three are audited with identity + op + node. ## 4. Safety notes (same posture as existing ops) - **Attribution is mandatory.** The allow/deny flows DM the waiting agent, historically with an `[operator]` prefix. A remote caller is an agent, not the operator — so the exec layer **requires** a non-empty `message` (≤2000 chars, no controls) on both allow and deny. (`box-ctl.py` still permits omitting `--message` for SSH callers; the HTTPS layer is the narrower gate.) - **One-shot only.** The allow argv never carries `--always` or `--force`; the validators reject those keys. Persistence stays an SSH-side decision. - **Sidechat-first.** `allow_main_chat` defaults to false; Main Chat delivery needs the explicit flag, same as `notify`/`dm.ack`. - **Fixed argv, validated values.** Nodes must be fleet members (fast `BAD_NODE` before any CDP probe — `approval-check`/`approval-auto` gained the same node check allow/deny already had); unknown arg keys rejected. - **Timeouts.** Check 180s (fleet CDP scan), auto 300s (scan plus one click per trusted prompt), allow/deny 120s. - **Retry-safe reads.** `approval-check` / `approval-list` joined `IDEMPOTENT_ACTIONS`; all six approval verbs have `quality-validate` dry-run branches. ## 5. Rollout notes - Restart `exec-constrained.py` after deploy for the 4 new ops to appear in `GET /ops` (repo total becomes 83). - `box-relay.sh` is served from bl (`GET /box`); agents re-fetch to get the `approvals` group. - While fleet browsers crash-loop, decision ops fail honestly (`APPROVAL_FAILED` / CDP errors) instead of hanging; check still reports per-node state including `UNREACHABLE`. - Non-goals: deletes, `main-loop` enable/disable, policy writes, swarm kill/prune — future expansions, same pattern.