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.
136 lines
5.6 KiB
Go
136 lines
5.6 KiB
Go
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")
|
||
}
|
||
}
|