feat(supervision): add choice watcher daemon, HTTPS spec docs, and test suites
- bin/muse_choice_watcher.py + systemd/muse-choices-reconcile.*: automatic choice answering and timer reconciliation - bin/digest.py: fleet log and health summarization - docs/BOX-*-HTTPS.md: comprehensive HTTPS execution contracts and API documentation - docs/MUSE-CHOICES-POLICY.md & docs/SUPERVISION-SPEC.md: autonomous execution specs - tests/test_*.py: unit test suites for HTTPS API, choice watcher, fleet heal, and swarm pruning
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user