iop/packages/go/agenttask/confinement_dispatch_test.go
toki 3c48879c44 feat(agent-runtime): 독립 Agent CLI 런타임 기반을 구현한다
독립 호스트에서 안전한 작업 실행과 복구를 제공하기 위해 런타임 설정, 정책, 상태 저장소, 워크스페이스 격리 및 AgentTask 오케스트레이션을 확장한다.
2026-07-29 13:12:21 +09:00

236 lines
7.4 KiB
Go

package agenttask
import (
"context"
"errors"
"reflect"
"strings"
"testing"
)
func TestValidatePreparedIsolationRequiresExactConfinementProof(t *testing.T) {
projectID := ProjectID("project")
workspaceID := WorkspaceID("workspace")
project := ProjectRecord{
ProjectID: projectID,
WorkspaceID: workspaceID,
Intent: &StartIntent{
ProjectID: projectID,
WorkspaceID: workspaceID,
ConfigRevision: "config-r1",
GrantRevision: "grant-r1",
WorkflowRevision: "workflow-r1",
},
}
work := WorkRecord{
Unit: testUnit("work", WriteSetUnknown),
AttemptID: "work#1",
}
target := ExecutionTarget{
ProviderID: "provider",
ModelID: "model",
ProfileID: "profile",
ProfileRevision: "profile-r1",
ConfigRevision: "config-r1",
Capacity: 1,
}
isolation := newFakeIsolation(t)
prepared, err := isolation.Prepare(context.Background(), IsolationRequest{
Project: project,
Work: work,
Target: target,
IdempotencyKey: "dispatch/project/work/1/isolation",
})
if err != nil {
t.Fatalf("Prepare: %v", err)
}
if _, _, err := validatePreparedIsolation(project, work, target, prepared); err != nil {
t.Fatalf("validate exact proof: %v", err)
}
missing := prepared
missing.Confinement = nil
if _, _, err := validatePreparedIsolation(project, work, target, missing); err == nil ||
!strings.Contains(err.Error(), "incomplete strict ports") {
t.Fatalf("missing proof error = %v", err)
}
tampered := prepared
tamperedProof := *prepared.Confinement.(*fakeConfinement)
tamperedProof.binding = prepared.Confinement.Binding()
tamperedProof.binding.ProfileRevision = "profile-r2"
tampered.Confinement = &tamperedProof
if _, _, err := validatePreparedIsolation(project, work, target, tampered); err == nil ||
!strings.Contains(err.Error(), "invalid executable confinement proof") {
t.Fatalf("tampered proof error = %v", err)
}
}
func TestManagerOwnsConfinementStartBeforeBindingProviderInvocation(t *testing.T) {
commandType := reflect.TypeOf(ConfinementCommand{})
for _, forbidden := range []string{"Stdin", "Stdout", "Stderr"} {
if _, exists := commandType.FieldByName(forbidden); exists {
t.Fatalf("ConfinementCommand still exposes caller-owned %s", forbidden)
}
}
snapshot := testSnapshot("project", "workspace", testUnit("work", WriteSetUnknown))
harness := newHarness(t, map[ProjectID]ProjectWorkflowSnapshot{"project": snapshot}, 1)
harness.start("project", "workspace", nil)
if err := harness.manager.Reconcile(context.Background()); err != nil {
t.Fatalf("Reconcile: %v", err)
}
proof := harness.isolation.proof(dispatchKey("project", "work", 1) + "/isolation")
if proof == nil {
t.Fatal("isolation did not retain the executable proof")
}
if proof.startCount() != 1 {
t.Fatalf("confinement starts = %d, want 1", proof.startCount())
}
commands := proof.startCommands()
if len(commands) != 1 || commands[0].Name != "true" {
t.Fatalf("confinement command = %#v, want exact prepared command", commands)
}
prepared, bound, proofStartsAtBind := harness.invoker.launchStats()
if prepared != 1 || bound != 1 || len(proofStartsAtBind) != 1 || proofStartsAtBind[0] != 1 {
t.Fatalf(
"launch ordering prepare=%d bind=%d proof-starts-at-bind=%v, want 1/1/[1]",
prepared,
bound,
proofStartsAtBind,
)
}
proofHandles := proof.startedHandles()
boundHandles := harness.invoker.startedHandles()
if len(proofHandles) != 1 || len(boundHandles) != 1 ||
proofHandles[0] != boundHandles[0] {
t.Fatalf(
"proof/bind handles = %#v/%#v, want one exact shared handle",
proofHandles,
boundHandles,
)
}
}
func TestManagerConfinementLaunchFailuresDoNotBindOrLeakChildren(t *testing.T) {
tests := []struct {
name string
configure func(*managerHarness)
wantStarts int
wantPrepare int
wantBind int
wantChild bool
wantHandle bool
wantAbort int
}{
{
name: "prepare failure",
configure: func(harness *managerHarness) {
harness.invoker.prepareErr = errors.New("prepare failed")
},
wantPrepare: 1,
},
{
name: "proof start failure",
configure: func(harness *managerHarness) {
harness.isolation.startErr = errors.New("proof start failed")
},
wantStarts: 1,
wantPrepare: 1,
},
{
name: "nil started handle",
configure: func(harness *managerHarness) {
harness.isolation.nilStarted = true
},
wantStarts: 1,
wantPrepare: 1,
},
{
name: "invalid started handle aborts child and pipes",
configure: func(harness *managerHarness) {
harness.isolation.invalidStart = true
harness.invoker.command = ConfinementCommand{Name: "sleep", Args: []string{"5"}}
},
wantStarts: 1,
wantPrepare: 1,
wantChild: true,
wantHandle: true,
wantAbort: 1,
},
{
name: "bind failure reaps child",
configure: func(harness *managerHarness) {
harness.invoker.bindErr = errors.New("bind failed")
harness.invoker.command = ConfinementCommand{Name: "sleep", Args: []string{"5"}}
},
wantStarts: 1,
wantPrepare: 1,
wantBind: 1,
wantChild: true,
wantHandle: true,
wantAbort: 1,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
snapshot := testSnapshot("project", "workspace", testUnit("work", WriteSetUnknown))
harness := newHarness(t, map[ProjectID]ProjectWorkflowSnapshot{"project": snapshot}, 1)
test.configure(harness)
harness.start("project", "workspace", nil)
if err := harness.manager.Reconcile(context.Background()); err != nil {
t.Fatalf("Reconcile: %v", err)
}
proof := harness.isolation.proof(dispatchKey("project", "work", 1) + "/isolation")
if proof == nil {
t.Fatal("isolation did not retain the executable proof")
}
prepared, bound, _ := harness.invoker.launchStats()
if proof.startCount() != test.wantStarts || prepared != test.wantPrepare || bound != test.wantBind {
t.Fatalf(
"starts/prepare/bind = %d/%d/%d, want %d/%d/%d",
proof.startCount(),
prepared,
bound,
test.wantStarts,
test.wantPrepare,
test.wantBind,
)
}
children := proof.startedChildren()
if test.wantChild {
if len(children) != 1 || children[0].ProcessState == nil {
t.Fatalf("bind failure leaked confined child: %#v", children)
}
} else if len(children) != 0 {
t.Fatalf("failed launch unexpectedly started children: %#v", children)
}
handles := proof.startedHandles()
if test.wantHandle {
if len(handles) != 1 {
t.Fatalf("started handles = %#v, want one", handles)
}
started, ok := handles[0].(*fakeStartedConfinement)
if !ok {
t.Fatalf("started handle type = %T", handles[0])
}
if started.abortCount() != test.wantAbort {
t.Fatalf("handle aborts = %d, want %d", started.abortCount(), test.wantAbort)
}
if _, err := started.stdin.Write([]byte("leak")); err == nil {
t.Fatal("partial-start cleanup left stdin open")
}
if _, err := started.stdout.Read(make([]byte, 1)); err == nil {
t.Fatal("partial-start cleanup left stdout open")
}
if _, err := started.stderr.Read(make([]byte, 1)); err == nil {
t.Fatal("partial-start cleanup left stderr open")
}
} else if len(handles) != 0 {
t.Fatalf("failed launch unexpectedly returned handles: %#v", handles)
}
if harness.invoker.callCount() != 0 {
t.Fatalf("failed launch submitted %d provider invocations", harness.invoker.callCount())
}
})
}
}