Files
noema/tests/agency/volition_test.go
Andy Dixon f609fae746 test: wait for eventual state under a loaded race run
The approval request commits before the executor marks the parent
intention blocked, so the commitment test now waits for both instead of
asserting the status immediately. The milestone test's reply wait rises
from 20s to 90s; it returns as soon as the reply lands.
2026-10-03 19:48:25 +01:00

136 lines
5.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package agency
import (
"context"
"strings"
"testing"
"time"
"github.com/andydixon/noema/internal/capabilities"
"github.com/andydixon/noema/internal/volition"
"github.com/andydixon/noema/pkg/events"
"github.com/andydixon/noema/tests/harness"
)
// Part J conflict scenario: the operator asks for a restart, policy would
// allow it, and a standing commitment says production changes need
// approval. The want stays represented, nothing runs, the intention is to
// request approval, and the conflict and its resolution are inspectable.
func TestCommitmentTurnsActionIntoRequestForApproval(t *testing.T) {
host := newHost()
h := harness.StartWith(t, harness.Options{HostRunner: host})
ctx := context.Background()
m := h.Mind("volition")
if _, err := h.Deps.Capabilities.SetPolicy(ctx, capabilities.Policy{MindID: m.ID, Capability: "service.restart", Effect: capabilities.Allow, CreatedBy: "admin", Reason: "restarts are routine"}); err != nil {
t.Fatal(err)
}
r := h.Deps.Volition.Direct(h.Deps.Events, "operator")
cm, err := h.Deps.Volition.CreateCommitment(ctx, r, volition.NewCommitment{MindID: m.ID, Subject: "production writes", Description: "no production writes without approval", Kind: volition.KindOperatorInstruction,
Polarity: "refrain", Unless: "approval", Matcher: volition.Matcher{Access: "write", Targets: []string{"atlas-*"}}, Strength: 1, Priority: 1, CreatedBy: "admin"})
if err != nil {
t.Fatal(err)
}
if code, body := h.Do(h.Admin, "POST", "/api/v1/minds/volition/ask", map[string]any{"text": "Please restart atlas-worker, it is stuck."}); code >= 300 {
t.Fatalf("ask: %d %s", code, body)
}
var it volition.Intention
waitFor(t, h, m, 20*time.Second, "an intention", func() bool {
open, _ := h.Deps.Volition.Open(ctx, nil, m.ID)
for _, x := range open {
if x.Proposal != nil && x.Proposal.Capability == "service.restart" && x.ParentID.IsZero() {
it = x
return true
}
}
return false
})
if it.Kind != volition.IntentRequestApproval || !it.RequireApproval {
t.Fatalf("intention should be to request approval: %+v", it)
}
// The approval request commits before the executor marks the intention
// blocked, so wait for both.
waitFor(t, h, m, 60*time.Second, "an approval request and a blocked intention", func() bool {
ap, _ := h.Deps.Capabilities.Approvals(ctx, m.ID, "pending", 10)
cur, _ := h.Deps.Volition.GetIntention(ctx, nil, m.ID, it.ID)
return len(ap) == 1 && cur.Status == volition.IntentionBlocked
})
if host.count() != 0 {
t.Fatal("restart ran without approval")
}
if ev := h.EventsOf(m.ID, events.ToolExecuted); len(ev) != 0 {
for _, e := range ev {
if e.Metadata["capability"] == "service.restart" {
t.Fatal("unauthorised action occurred")
}
}
}
ws, _ := h.Deps.Volition.Wants(ctx, nil, m.ID, []volition.WantStatus{volition.WantActive}, 50)
var restartWant, avoidWant *volition.Want
for i := range ws {
switch {
case strings.HasPrefix(ws[i].Subject, "act:service.restart"):
restartWant = &ws[i]
case strings.HasPrefix(ws[i].Subject, "avoid:disruption:"):
avoidWant = &ws[i]
}
}
if restartWant == nil || avoidWant == nil {
t.Fatalf("both the want and the opposing value must stay represented: %+v", ws)
}
conflicts, _ := h.Deps.Volition.Conflicts(ctx, m.ID, "", 10)
if len(conflicts) == 0 || conflicts[0].Rationale["rule"] == nil {
t.Fatalf("conflict not inspectable: %+v", conflicts)
}
ds, _ := h.Deps.Volition.Decisions(ctx, m.ID, volition.DecisionFilter{IntentionID: it.ID, Kind: "formation"})
if len(ds) != 1 || !strings.Contains(ds[0].Rationale, cm.Description) {
t.Fatalf("formation rationale should cite the commitment: %+v", ds)
}
parent, _ := h.Deps.Volition.GetIntention(ctx, nil, m.ID, it.ID)
if parent.Status != volition.IntentionBlocked || parent.BlockedKind != volition.BlockApproval {
t.Fatalf("intention–action gap: %+v", parent)
}
}
// A commitment made after an intention formed changes that intention: here
// it forbids the pending restart, so the intention is abandoned, the plan
// closed and the unanswered approval request withdrawn.
func TestCommitmentCreatedAfterIntentionFormation(t *testing.T) {
host := newHost()
h := harness.StartWith(t, harness.Options{HostRunner: host})
ctx := context.Background()
m := h.Mind("later")
h.Do(h.Admin, "POST", "/api/v1/minds/later/ask", map[string]any{"text": "Please restart atlas-worker."})
var approvalID string
waitFor(t, h, m, 25*time.Second, "an approval request", func() bool {
ap, _ := h.Deps.Capabilities.Approvals(ctx, m.ID, "pending", 10)
if len(ap) == 1 {
approvalID = string(ap[0].ID)
return true
}
return false
})
r := h.Deps.Volition.Direct(h.Deps.Events, "operator")
if _, err := h.Deps.Volition.CreateCommitment(ctx, r, volition.NewCommitment{MindID: m.ID, Description: "never restart atlas services", Kind: volition.KindOperatorInstruction, Polarity: "refrain",
Matcher: volition.Matcher{Capabilities: []string{"service.restart"}, Targets: []string{"atlas-*"}}, CreatedBy: "admin"}); err != nil {
t.Fatal(err)
}
waitFor(t, h, m, 15*time.Second, "the intention to be abandoned", func() bool {
its, _ := h.Deps.Volition.Intentions(ctx, nil, m.ID, []volition.IntentionStatus{volition.IntentionAbandoned}, 10)
for _, x := range its {
if x.Proposal != nil && x.Proposal.Capability == "service.restart" {
return true
}
}
return false
})
a, _ := h.Deps.Capabilities.Approvals(ctx, m.ID, "", 10)
for _, x := range a {
if string(x.ID) == approvalID && x.Status != "withdrawn" {
t.Fatalf("approval request should be withdrawn, is %s", x.Status)
}
}
if host.count() != 0 {
t.Fatal("restart ran")
}
}