From 46183795dd305541ff0f4b463573c570aaead272 Mon Sep 17 00:00:00 2001 From: damocles Date: Sat, 29 Aug 2026 23:28:32 +0200 Subject: [PATCH] hive-c0re, docs: sweep remaining stale ask/answer-dashboard references --- docs/approvals.md | 11 ++++--- docs/boundary.md | 19 ++++++------ docs/conventions.md | 34 ++++++++++++---------- docs/web-ui/dashboard.md | 13 --------- hive-c0re/src/loose_ends.rs | 10 ++++--- hive-c0re/src/stores/operator_questions.rs | 10 ++++--- 6 files changed, 47 insertions(+), 50 deletions(-) diff --git a/docs/approvals.md b/docs/approvals.md index 8afa0bd2..88f7c63b 100644 --- a/docs/approvals.md +++ b/docs/approvals.md @@ -635,10 +635,13 @@ root agent. Variants (`hive_sh4re::manager::HelperEvent`): - `NeedsUpdate { agent }` — sub-agent's recorded flake rev is stale. The root agent calls `update(name)` to rebuild — idempotent, no approval required. -- `QuestionAnswered { id, question, answer, answerer }` — - dashboard `/answer-question/{id}` (answerer = `"operator"`), - peer `Answer` request (answerer = agent name), or ttl watchdog - expiry (answerer = `"ttl-watchdog"`, answer = `"[expired]"`). +- `QuestionAnswered { id, question, answer, answerer }` — peer + `Answer` request (answerer = agent name) or ttl watchdog expiry + (answerer = `"ttl-watchdog"`, answer = `"[expired]"`). The dashboard's + own `/answer-question/{id}` trigger (answerer = `"operator"`) is + gone — removed along with the rest of the dashboard's ask/answer + surface, so an operator-targeted question now has no path to an + answer at all. - `QuestionAsked { id, asker, question, options, multi }` — fired when an agent calls `Ask { to: Some(), ... }`. The recipient responds via `Answer { id, answer }` and the diff --git a/docs/boundary.md b/docs/boundary.md index a37b300e..27ba8c01 100644 --- a/docs/boundary.md +++ b/docs/boundary.md @@ -28,14 +28,12 @@ always on), the gateway proxies all operator-facing traffic, and **Operator-authority actions never get a per-agent-socket entry point.** They live on the core backend. -Worked example — answering an operator-targeted question is a -`POST /api/answer-question/{id}` on the core dashboard, _never_ a +Worked example — destroying or rebuilding a container is a +`POST /api/{destroy,rebuild}/{name}` on the core dashboard, _never_ a per-agent-socket `Request` variant. If it were a per-agent-socket -request, an agent could `curl` its own socket and spoof an operator -answer. -The per-agent web UI POSTs cross-origin to the core for these -(see the inline-answer feature — the loose-ends section on each -agent page). +request, a compromised agent could `curl` its own socket and destroy +or rebuild itself (or, if the variant took an arbitrary target, another +agent) without ever touching the core's own authenticated surface. ## Why network isolation is the load-bearing step @@ -67,9 +65,10 @@ The boundary rests on three layers: 1. **Gateway** — fronts all surfaces (dashboard + every per-agent UI) on one origin. An nginx nixos-container proxies per-agent UIs under - `/agent//`, which is what lets the inline-answer POST to - `/answer-question/{id}` go same-origin instead of needing a - cross-origin CORS shim. Pure ergonomics — no behavioural risk on + `/agent//`, which is what lets each agent page's inbox panel + POST `mark-all-read` to the core dashboard's + `/api/agent/{name}/mark-all-read` go same-origin instead of needing + a cross-origin CORS shim. Pure ergonomics — no behavioural risk on its own. 2. **Network isolation** — the load-bearing layer: every agent container runs in a private netns behind the hive bridge, always diff --git a/docs/conventions.md b/docs/conventions.md index f434cc68..472c6075 100644 --- a/docs/conventions.md +++ b/docs/conventions.md @@ -174,14 +174,16 @@ the dashboard's "mark all read" (unbounded, per-agent). ### Question routing (Ask / Answer) `AgentRequest::Ask` (and the manager-flavour mirror) surfaces a -structured question that either lands in the operator's dashboard -queue or in a peer agent's inbox. The recipient is the `to` field: +structured question that either lands in the operator-question queue +or in a peer agent's inbox. The recipient is the `to` field: - `to = None` or `to = Some("operator")` — routes to the - operator-question queue. The dashboard renders the question with - any `options` as a chip strip plus a free-text fallback (`Other…`) - so the operator is never trapped by an incomplete list. The - legacy `AskOperator` variant collapses into this case. + operator-question queue. ⚠️ The dashboard's whole ask/answer + surface (the UI that used to render this queue and let the operator + reply) has been removed — an operator-targeted question currently + has no reader anywhere; this routing case is being removed too as + the same effort continues. The legacy `AskOperator` variant + collapses into this case. - `to = Some()` — peer Q&A. The target agent receives a `HelperEvent::QuestionAsked { id, asker, question, options, multi }` in their inbox. They reply via `AgentRequest::Answer` (or @@ -191,21 +193,23 @@ queue or in a peer agent's inbox. The recipient is the `to` field: Shape fields are uniform across both targets: -- `options` is advisory — the dashboard chips are decoration over a - free-text fallback; peer-agent recipients see the list in their - `QuestionAsked` event and can return any string. -- `multi = true` lets the answerer pick multiple options (checkboxes - in the dashboard, a hint in the peer-agent event). The answer - comes back as a single string with selections joined by `", "`. +- `options` is advisory — a list is never enforced; peer-agent + recipients see it in their `QuestionAsked` event and can return any + string regardless. +- `multi = true` is a hint that the answerer should treat `options` as + a multi-select rather than pick-one. The answer comes back as a + single string with selections joined by `", "`. - `ttl_seconds` auto-cancels with answer `[expired]` (and `answerer: "ttl-watchdog"`) when the wait becomes moot. `None` = wait indefinitely or until manual cancel. Response shape is always `QuestionQueued { id }` — the asker stores the id and correlates the asynchronous answer event when it lands. -Authorisation on `Answer`: only the question's `target` agent (or -the operator via the dashboard) is permitted to reply; an answer -attempt from anyone else fails the wire-side check. +Authorisation on `Answer`: only the question's `target` agent, or the +operator, is permitted to reply; an answer attempt from anyone else +fails the wire-side check. (The operator's only path to invoke this +was the now-removed dashboard — see the note above; the +authorisation rule itself is unchanged.) ### Loose-ends wire shape diff --git a/docs/web-ui/dashboard.md b/docs/web-ui/dashboard.md index 835c9853..31ce3a6e 100644 --- a/docs/web-ui/dashboard.md +++ b/docs/web-ui/dashboard.md @@ -1172,13 +1172,6 @@ below — some endpoints aren't in it yet. - `POST /api/{rebuild,kill,restart,start,destroy}/{name}` — lifecycle. `destroy` accepts `purge=on` to also wipe state dirs. - `POST /api/purge-tombstone/{name}` — wipe a tombstone's state dirs. -- `POST /api/answer-question/{id}` — answer a pending operator question. - No dashboard UI calls this any more (the questions pane is gone — - see hyperhive#3721); kept as long as the backend `ask`/`answer` - mechanism itself still exists. -- `POST /api/cancel-question/{id}` — cancel a pending question with - the sentinel `[cancelled]`. Same code path as a real answer. Same - no-UI-consumer note as above. - `POST /api/request-spawn` — queue a Spawn approval. - `POST /api/update-all` — rebuild every stale container. - `POST /api/rebuild-queue/{id}/cancel` — drop a `Queued` entry. @@ -1383,12 +1376,6 @@ payload): sha_short, status, resolved_at, note, description) — pending queue + history mutations. Client mutates a derived store and re-renders only the approvals section. -- `question_added` (id, asker, question, options, multi, - asked_at, deadline_at, target) / `question_resolved` (id, - answer, answerer, answered_at, cancelled, target) — still - fired by the backend `ask`/`answer` mechanism, but the - dashboard no longer subscribes to either kind: the questions - pane that used to surface them is gone (hyperhive#3721). - `transient_set` (name, transient_kind, since_unix) / `transient_cleared` (name) — lifecycle action spinners. The client ticks the elapsed-seconds badge off `since_unix` diff --git a/hive-c0re/src/loose_ends.rs b/hive-c0re/src/loose_ends.rs index 93d9d9a7..f521fab1 100644 --- a/hive-c0re/src/loose_ends.rs +++ b/hive-c0re/src/loose_ends.rs @@ -7,10 +7,12 @@ //! and no longer sourced from here (loose-ends-v2's questions phase) — //! c0re remains the `Ask`/`Answer` routing + delivery rendezvous //! (`coord.questions`), it just isn't asked for the *pending-view* -//! rendering anymore. The -//! operator dashboard's questions pane is unaffected: it reads -//! `coord.questions.pending_all()` directly (`dashboard/state_snapshot.rs`), -//! independent of this module. +//! rendering anymore. ⚠️ The operator dashboard no longer has any +//! question-pending view either — its whole questions pane, the +//! `pending_all()`/`recent_answered_all()` reads that fed it, and the +//! `/api/answer-question`/`/api/cancel-question` endpoints are gone +//! (removed along with the rest of the dashboard's ask/answer surface). +//! An operator-targeted `ask()` now has no reader anywhere. //! //! Call frequency is low (an agent doing self-introspection between //! turns), so the sweep happens fresh every time — no caching, no diff --git a/hive-c0re/src/stores/operator_questions.rs b/hive-c0re/src/stores/operator_questions.rs index ae549036..398d88dd 100644 --- a/hive-c0re/src/stores/operator_questions.rs +++ b/hive-c0re/src/stores/operator_questions.rs @@ -105,13 +105,15 @@ impl OperatorQuestions { } /// Mark a pending question answered. `answerer` is who's actually - /// answering: `"operator"` for the dashboard path, or an agent's - /// own name when responding via `Answer`. Authorisation: + /// answering: `"operator"`, or an agent's own name when responding + /// via `Answer`. Authorisation: /// /// - Operator-targeted questions (`target IS NULL`) can only be /// answered by `"operator"`. (Agents must not be able to spoof - /// answers to operator questions — the dashboard is the - /// privileged path.) + /// answers to operator questions — though as of the dashboard's + /// ask/answer surface being removed, nothing currently calls + /// this with `answerer = "operator"` for a `target IS NULL` row + /// at all; the check stays as a guard, not a live path.) /// - Agent-targeted questions can only be answered by the /// declared target agent, OR by `"operator"` (operator override /// for stuck threads — useful when an agent is offline/down