security: add the capability and policy layer as the only path to action

Phase 12 (ADR-0005). Capabilities are registered in code with parameter
schemas, risk classes and default effects; sudo_shell and delete_database
are locked to DENY and cannot be relaxed. Policies are scoped globally or
per mind with capability, target glob, action kind and optional hour
window; evaluation is deterministic, most specific wins, and ties go to
the stricter effect. Invoke is the single entry point: it refuses
dreaming and replay contexts, validates parameters (unknown keys, non
http URLs and path traversal rejected), evaluates policy and session
grants, hashes the exact request and records a policy_decision event.
ALLOW executes with timeout and panic isolation and turns textual output
into a tool-trust observation; ASK creates an approval request bound to
the hash with an alert notification; DENY is counted and recorded.

Operators approve once, approve for a time-boxed session grant, deny
with a note, or modify parameters (re-validated, re-hashed, original
kept in the append-only decision log). The self-model's capability list
is recomputed from effective policy on start and on every policy change.
The act process maps fetch requests to http.fetch and the action handler
invokes through the layer; replies report capabilities and outcomes.

Policy matrix and approval centre pages, dashboard count, API, CLI
(approvals, policy, invoke) and docs/capabilities.md.
This commit is contained in:
Andy Dixon
2026-06-29 20:28:05 +01:00
parent 99a89ba847
commit b35dbe0299
29 changed files with 2318 additions and 23 deletions
+2
View File
@@ -78,6 +78,8 @@ func Rules(ev events.Event, cfg minds.CognitiveConfig, p minds.Personality) []Ca
if ev.Metadata["check"] == "overconfidence" || ev.Metadata["check"] == "loop" {
out = append(out, Candidate{Level: "warn", Title: "Metacognitive warning", Body: ev.Summary, Rule: "metacognition", Reason: map[string]any{"check": ev.Metadata["check"]}, AggregateKey: "meta:" + str(ev.Metadata["check"]), SourceEvent: ev.ID})
}
case events.ToolRequested:
out = append(out, Candidate{Level: "alert", Title: "An action needs your approval", Body: ev.Summary, Rule: "tool_requested", Reason: map[string]any{"risk": ev.Metadata["risk"]}, AggregateKey: "approval:" + str(ev.Metadata["approval_id"]), SourceEvent: ev.ID})
case events.ToolDenied:
out = append(out, Candidate{Level: "alert", Title: "An action was denied by policy", Body: ev.Summary, Rule: "tool_denied", Reason: map[string]any{}, AggregateKey: "denied:" + str(ev.Metadata["capability"]), SourceEvent: ev.ID})
case events.Observation: