93 lines
4.0 KiB
Markdown
93 lines
4.0 KiB
Markdown
|
|
# 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 <node> --message <text> [--allow-main-chat]
|
||
|
|
box approvals deny <node> --message <text> [--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.
|