tool: risk tiers decide the confirm, not one boolean (V-449)
The Destructive column was a mechanism with no policy behind it: nothing said which acts are destructive, whether a confirmed act stays confirmed, or what a new tool domain inherits, so each domain answered for itself. Three tiers, derived from the row rather than stored, so the answer can be argued with in one place instead of being whatever the last person to tick the checkbox believed. Safe runs. Destructive costs a confirm turn, every time — a confirmation binds one capability, one target and one argument list, and it dies with the parked turn. Irreversible is refused: a confirm turn there would be theatre, because the STT, the router and the fuzzy allowlist match are all guesses and a spoken "да" checks none of them. She names the gap; the row stays enabled. An unrecognised dispatch shape inherits destructive, not safe. A domain argues its way down to running freely, never up to being gated.
This commit is contained in:
@@ -51,6 +51,12 @@ func (h *reactiveHandler) actionAct(ctx context.Context, dec router.Decision) st
|
||||
phrase := actPhrase(dec.Slots.Fn, dec.Slots.Args)
|
||||
h.park(dec.Slots.Fn, dec.Slots.Args, phrase)
|
||||
return "выполнить «" + phrase + "»? скажи «да» или «нет»."
|
||||
case errors.Is(err, tool.ErrNeedsAuthedSurface):
|
||||
// Irreversible (internal/tool/risk.go). A confirm turn would not
|
||||
// help: everything that proposed this act — the STT, the router,
|
||||
// the fuzzy allowlist match — is a guess, and a spoken "да" checks
|
||||
// none of it. She names the gap instead.
|
||||
return "это я из голоса не выполню — после него ничего не вернуть. запусти сам, если правда надо."
|
||||
case errors.Is(err, tool.ErrNotEnabled):
|
||||
return h.proposeGap(ctx, dec)
|
||||
case errors.Is(err, tool.ErrNotConnected), errors.Is(err, mcp.ErrNotConnected), errors.Is(err, mcp.ErrNoServer):
|
||||
|
||||
Reference in New Issue
Block a user