444 lines
13 KiB
Go
444 lines
13 KiB
Go
package taskloop
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"iop/packages/go/agentconfig"
|
|
"iop/packages/go/agentguard"
|
|
"iop/packages/go/agentpolicy"
|
|
clistatus "iop/packages/go/agentprovider/cli/status"
|
|
"iop/packages/go/agentruntime"
|
|
"iop/packages/go/agenttask"
|
|
)
|
|
|
|
func TestProviderEnvironmentConfinesCodexRuntimeState(t *testing.T) {
|
|
sourceHome := canonicalTempDir(t)
|
|
for _, name := range []string{"auth.json", "config.toml"} {
|
|
if err := os.WriteFile(
|
|
filepath.Join(sourceHome, name),
|
|
[]byte(name+"\n"),
|
|
0o600,
|
|
); err != nil {
|
|
t.Fatalf("write %s: %v", name, err)
|
|
}
|
|
}
|
|
t.Setenv("CODEX_HOME", sourceHome)
|
|
taskRoot := canonicalTempDir(t)
|
|
binding := agenttask.ConfinementBinding{
|
|
TaskRoot: taskRoot,
|
|
WorkingDir: filepath.Join(taskRoot, "view"),
|
|
WritableRoots: []string{
|
|
filepath.Join(taskRoot, "view"),
|
|
filepath.Join(taskRoot, "tmp"),
|
|
filepath.Join(taskRoot, "cache"),
|
|
},
|
|
}
|
|
for _, root := range binding.WritableRoots {
|
|
if err := os.MkdirAll(root, 0o700); err != nil {
|
|
t.Fatalf("create writable root: %v", err)
|
|
}
|
|
}
|
|
resolved := agentconfig.ResolvedProfile{
|
|
Provider: agentconfig.Provider{ID: "codex"},
|
|
Profile: agentconfig.Profile{Env: []string{"FIXTURE=present"}},
|
|
}
|
|
environment, err := providerEnvironment(
|
|
resolved,
|
|
binding,
|
|
binding.WorkingDir,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("providerEnvironment: %v", err)
|
|
}
|
|
values := make(map[string]string)
|
|
for _, entry := range environment {
|
|
key, value, found := strings.Cut(entry, "=")
|
|
if found {
|
|
values[key] = value
|
|
}
|
|
}
|
|
codexHome := filepath.Join(binding.WritableRoots[2], "codex-home")
|
|
if values["CODEX_HOME"] != codexHome ||
|
|
values["TMPDIR"] != binding.WritableRoots[1] ||
|
|
values["TMP"] != binding.WritableRoots[1] ||
|
|
values["TEMP"] != binding.WritableRoots[1] ||
|
|
values["PWD"] != binding.WorkingDir ||
|
|
values["IOP_AGENT_TASK_ROOT"] != binding.TaskRoot ||
|
|
values["FIXTURE"] != "present" {
|
|
t.Fatalf("provider environment is not confined: %#v", values)
|
|
}
|
|
for _, name := range []string{"auth.json", "config.toml"} {
|
|
target, err := os.Readlink(filepath.Join(codexHome, name))
|
|
if err != nil {
|
|
t.Fatalf("read runtime link %s: %v", name, err)
|
|
}
|
|
if target != filepath.Join(sourceHome, name) {
|
|
t.Fatalf("runtime link %s = %q", name, target)
|
|
}
|
|
}
|
|
if _, err := providerEnvironment(resolved, binding, binding.WorkingDir); err != nil {
|
|
t.Fatalf("idempotent providerEnvironment: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestProviderPreparesConfinedCommandAndOwnsExactStartedChild(t *testing.T) {
|
|
provider, err := NewProvider(testCatalog())
|
|
if err != nil {
|
|
t.Fatalf("NewProvider: %v", err)
|
|
}
|
|
work := agenttask.WorkRecord{
|
|
Unit: agenttask.WorkUnit{
|
|
ID: "1",
|
|
Metadata: map[string]string{
|
|
"plan_path": "agent-task/m-m1/1_fixture/PLAN-test.md",
|
|
"review_path": "agent-task/m-m1/1_fixture/CODE_REVIEW-test.md",
|
|
},
|
|
},
|
|
AttemptID: "attempt-1",
|
|
}
|
|
project := agenttask.ProjectRecord{
|
|
ProjectID: "project-a", WorkspaceID: "workspace-a",
|
|
}
|
|
target := agenttask.ExecutionTarget{
|
|
ProviderID: "test-provider", ModelID: "test-model", ProfileID: "p1",
|
|
}
|
|
resolved, ok := testCatalog().ResolveProfile(target.ProfileID)
|
|
if !ok {
|
|
t.Fatal("test profile is missing")
|
|
}
|
|
target.ProfileRevision = guardProfile(resolved).Revision
|
|
binding := agenttask.ConfinementBinding{
|
|
Revision: "confinement-r1",
|
|
ProfileRevision: target.ProfileRevision,
|
|
TaskRoot: "/tmp/task",
|
|
}
|
|
launch, err := provider.Prepare(context.Background(), agenttask.DispatchRequest{
|
|
Project: project, Work: work, Target: target,
|
|
Permit: &agentguard.Permit{},
|
|
Confinement: fakeConfinement{binding: binding},
|
|
Workspace: agentguard.CanonicalWorkspace{
|
|
WorkingDir: "/tmp/task/view", WritableRoots: []string{"/tmp/task/view"},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Prepare: %v", err)
|
|
}
|
|
command := launch.Command()
|
|
if command.Name != "provider-must-not-run-in-tests" ||
|
|
!strings.Contains(command.Args[len(command.Args)-1], "PLAN-test.md") ||
|
|
!strings.Contains(command.Args[len(command.Args)-1], "CODE_REVIEW-test.md") {
|
|
t.Fatalf("prepared command = %#v", command)
|
|
}
|
|
if _, err := launch.BindStarted(nil); err == nil {
|
|
t.Fatal("BindStarted accepted a missing confinement child")
|
|
}
|
|
|
|
child := exec.Command("/bin/sh", "-c", "cat >/dev/null")
|
|
stdin, err := child.StdinPipe()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
stdout, err := child.StdoutPipe()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
stderr, err := child.StderrPipe()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := child.Start(); err != nil {
|
|
t.Fatalf("start deterministic fixture child: %v", err)
|
|
}
|
|
started := &fakeStartedConfinement{
|
|
child: child, stdin: stdin, stdout: stdout, stderr: stderr,
|
|
}
|
|
invocation, err := launch.BindStarted(started)
|
|
if err != nil {
|
|
_ = child.Process.Kill()
|
|
t.Fatalf("BindStarted: %v", err)
|
|
}
|
|
locators := invocation.Locators()
|
|
if len(locators) != 1 ||
|
|
locators[0].Kind != agenttask.LocatorProcess ||
|
|
locators[0].ProjectID != project.ProjectID ||
|
|
locators[0].WorkUnitID != work.Unit.ID {
|
|
t.Fatalf("locators = %#v", locators)
|
|
}
|
|
submission, err := invocation.Wait(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("Wait: %v", err)
|
|
}
|
|
if !submission.Ready || submission.ArtifactID != artifactIdentity(work) {
|
|
t.Fatalf("submission = %#v", submission)
|
|
}
|
|
}
|
|
|
|
func TestProviderPersistsPiNativeSessionBeforeWait(t *testing.T) {
|
|
providerCatalog := piTestCatalog()
|
|
provider, err := NewProvider(providerCatalog)
|
|
if err != nil {
|
|
t.Fatalf("NewProvider: %v", err)
|
|
}
|
|
resolved, ok := providerCatalog.ResolveProfile("pi-headless")
|
|
if !ok {
|
|
t.Fatal("Pi test profile is missing")
|
|
}
|
|
target := agenttask.ExecutionTarget{
|
|
ProviderID: resolved.Provider.ID,
|
|
ModelID: resolved.Model.ID,
|
|
ProfileID: resolved.Profile.ID,
|
|
ProfileRevision: guardProfile(resolved).Revision,
|
|
ConfigRevision: "config-r1",
|
|
Capacity: 1,
|
|
}
|
|
root := canonicalTempDir(t)
|
|
binding := agenttask.ConfinementBinding{
|
|
Revision: "confinement-r1",
|
|
IsolationID: "overlay:pi",
|
|
IsolationRevision: "overlay-r1",
|
|
PinnedBaseRevision: "snapshot-r1",
|
|
ConfigRevision: "config-r1",
|
|
GrantRevision: "grant-r1",
|
|
ProfileRevision: target.ProfileRevision,
|
|
BaseRoot: filepath.Join(root, "base"),
|
|
RuntimeRoot: root,
|
|
SnapshotRoot: filepath.Join(root, "snapshot"),
|
|
TaskRoot: filepath.Join(root, "task"),
|
|
WorkingDir: filepath.Join(root, "task", "view"),
|
|
WritableRoots: []string{
|
|
filepath.Join(root, "task", "view"),
|
|
filepath.Join(root, "task", "tmp"),
|
|
filepath.Join(root, "task", "cache"),
|
|
},
|
|
}
|
|
project := agenttask.ProjectRecord{ProjectID: "p", WorkspaceID: "w"}
|
|
work := agenttask.WorkRecord{
|
|
Unit: agenttask.WorkUnit{
|
|
ID: "1",
|
|
Metadata: map[string]string{
|
|
"plan_path": "agent-task/m-m/1/PLAN-cloud-G10.md",
|
|
"review_path": "agent-task/m-m/1/CODE_REVIEW-cloud-G10.md",
|
|
},
|
|
},
|
|
AttemptID: "attempt-1",
|
|
}
|
|
launch, err := provider.Prepare(context.Background(), agenttask.DispatchRequest{
|
|
Project: project,
|
|
Work: work,
|
|
Target: target,
|
|
Permit: &agentguard.Permit{},
|
|
Confinement: fakeConfinement{
|
|
binding: binding,
|
|
},
|
|
Workspace: agentguard.CanonicalWorkspace{
|
|
WorkingDir: binding.WorkingDir,
|
|
WritableRoots: append(
|
|
[]string(nil),
|
|
binding.WritableRoots...,
|
|
),
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Prepare: %v", err)
|
|
}
|
|
command := launch.Command()
|
|
sessionIndex := -1
|
|
for index, argument := range command.Args {
|
|
if argument == "--session-id" {
|
|
sessionIndex = index
|
|
break
|
|
}
|
|
}
|
|
if sessionIndex < 0 || sessionIndex+3 >= len(command.Args) ||
|
|
command.Args[sessionIndex+2] != "--session-dir" {
|
|
t.Fatalf("Pi command has no durable session arguments: %#v", command.Args)
|
|
}
|
|
started, err := startFixtureCommand(context.Background(), "cat >/dev/null")
|
|
if err != nil {
|
|
t.Fatalf("start fixture: %v", err)
|
|
}
|
|
invocation, err := launch.BindStarted(started)
|
|
if err != nil {
|
|
_ = started.Abort()
|
|
t.Fatalf("BindStarted: %v", err)
|
|
}
|
|
locators := invocation.Locators()
|
|
if len(locators) != 2 || locators[1].Kind != agenttask.LocatorSession {
|
|
t.Fatalf("Pi locators = %#v", locators)
|
|
}
|
|
var reference nativeSessionReference
|
|
if err := json.Unmarshal([]byte(locators[1].Opaque), &reference); err != nil {
|
|
t.Fatalf("decode native session locator: %v", err)
|
|
}
|
|
if reference.SessionID != command.Args[sessionIndex+1] ||
|
|
reference.SessionDirectory != command.Args[sessionIndex+3] ||
|
|
locators[1].Revision != digestStrings(
|
|
"native-session-locator",
|
|
locators[1].Opaque,
|
|
) {
|
|
t.Fatalf("native session locator/command mismatch: %#v / %#v", reference, command.Args)
|
|
}
|
|
if _, err := invocation.Wait(context.Background()); err != nil {
|
|
t.Fatalf("Wait: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestWorkerPromptPreservesActiveArtifactsForOfficialReview(t *testing.T) {
|
|
plan := "agent-task/m-m/1/PLAN-cloud-G10.md"
|
|
review := "agent-task/m-m/1/CODE_REVIEW-cloud-G10.md"
|
|
prompt := workerPrompt(plan, review)
|
|
for _, required := range []string{
|
|
plan,
|
|
review,
|
|
"direct implementing-worker assignment",
|
|
"do not invoke a task-loop dispatcher",
|
|
"Do not append a review verdict",
|
|
"archive, move, rename, or delete either active artifact",
|
|
} {
|
|
if !strings.Contains(prompt, required) {
|
|
t.Fatalf("worker prompt does not contain %q: %q", required, prompt)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestProviderFailureObservationFeedsCommonContinuationIdentity(t *testing.T) {
|
|
providerCatalog := testCatalog()
|
|
observer := matchingQuotaObserver{}
|
|
provider, err := NewProvider(providerCatalog, observer)
|
|
if err != nil {
|
|
t.Fatalf("NewProvider: %v", err)
|
|
}
|
|
resolved, ok := providerCatalog.ResolveProfile("p1")
|
|
if !ok {
|
|
t.Fatal("test profile is missing")
|
|
}
|
|
target := agenttask.ExecutionTarget{
|
|
ProviderID: resolved.Provider.ID,
|
|
ModelID: resolved.Model.ID,
|
|
ProfileID: resolved.Profile.ID,
|
|
ProfileRevision: guardProfile(resolved).Revision,
|
|
ConfigRevision: "config-r1",
|
|
Capacity: 1,
|
|
}
|
|
binding := agenttask.ConfinementBinding{
|
|
Revision: "confinement-r1",
|
|
ProfileRevision: target.ProfileRevision,
|
|
TaskRoot: "/tmp/task",
|
|
}
|
|
work := agenttask.WorkRecord{
|
|
Unit: agenttask.WorkUnit{
|
|
ID: "work",
|
|
Metadata: map[string]string{
|
|
"plan_path": "agent-task/m/PLAN-local-G01.md",
|
|
"review_path": "agent-task/m/CODE_REVIEW-local-G01.md",
|
|
},
|
|
},
|
|
AttemptID: "attempt-1",
|
|
}
|
|
launch, err := provider.Prepare(context.Background(), agenttask.DispatchRequest{
|
|
Project: agenttask.ProjectRecord{ProjectID: "p", WorkspaceID: "w"},
|
|
Work: work,
|
|
Target: target,
|
|
Permit: &agentguard.Permit{},
|
|
Confinement: fakeConfinement{
|
|
binding: binding,
|
|
},
|
|
Workspace: agentguard.CanonicalWorkspace{WorkingDir: "/tmp/task"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Prepare: %v", err)
|
|
}
|
|
started, err := startFixtureCommand(context.Background(), "exit 3")
|
|
if err != nil {
|
|
t.Fatalf("start fixture: %v", err)
|
|
}
|
|
invocation, err := launch.BindStarted(started)
|
|
if err != nil {
|
|
_ = started.Abort()
|
|
t.Fatalf("BindStarted: %v", err)
|
|
}
|
|
if _, err := invocation.Wait(context.Background()); err == nil {
|
|
t.Fatal("failed provider fixture returned success")
|
|
}
|
|
observed, ok := invocation.(agentruntimeFailureObservedInvocation)
|
|
if !ok {
|
|
t.Fatal("provider invocation does not expose a normalized failure observation")
|
|
}
|
|
observation := observed.FailureObservation()
|
|
if observation.Failure.Code != agentruntime.FailureCodeProcessExit ||
|
|
!observation.Failure.Retryable ||
|
|
observation.Quota.Adapter != target.ProviderID ||
|
|
observation.Quota.Target != target.ProfileID ||
|
|
observation.Quota.Validity != agentpolicy.ObservationValid {
|
|
t.Fatalf("failure observation = %#v", observation)
|
|
}
|
|
}
|
|
|
|
type agentruntimeFailureObservedInvocation interface {
|
|
FailureObservation() agentpolicy.AttemptObservation
|
|
}
|
|
|
|
type matchingQuotaObserver struct{}
|
|
|
|
func (matchingQuotaObserver) ObserveQuota(
|
|
_ context.Context,
|
|
target agenttask.ExecutionTarget,
|
|
observedAt time.Time,
|
|
) (agentpolicy.QuotaObservation, error) {
|
|
snapshot := clistatus.NormalizeQuotaSnapshot(
|
|
target.ProviderID,
|
|
target.ProfileID,
|
|
nil,
|
|
observedAt,
|
|
nil,
|
|
nil,
|
|
)
|
|
return agentpolicy.NormalizeQuotaObservation(snapshot, observedAt, time.Minute), nil
|
|
}
|
|
|
|
type fakeConfinement struct {
|
|
binding agenttask.ConfinementBinding
|
|
}
|
|
|
|
func (confinement fakeConfinement) Revision() string {
|
|
return confinement.binding.Revision
|
|
}
|
|
func (confinement fakeConfinement) Binding() agenttask.ConfinementBinding {
|
|
return confinement.binding
|
|
}
|
|
func (confinement fakeConfinement) Validate(agenttask.ConfinementBinding) error {
|
|
return nil
|
|
}
|
|
func (confinement fakeConfinement) Start(
|
|
context.Context,
|
|
agenttask.ConfinementCommand,
|
|
) (agenttask.StartedConfinement, error) {
|
|
panic("fixture confinement must not launch the declared provider command")
|
|
}
|
|
|
|
type fakeStartedConfinement struct {
|
|
child *exec.Cmd
|
|
stdin io.WriteCloser
|
|
stdout io.ReadCloser
|
|
stderr io.ReadCloser
|
|
}
|
|
|
|
func (started *fakeStartedConfinement) Child() *exec.Cmd { return started.child }
|
|
func (started *fakeStartedConfinement) Stdin() io.WriteCloser { return started.stdin }
|
|
func (started *fakeStartedConfinement) Stdout() io.ReadCloser { return started.stdout }
|
|
func (started *fakeStartedConfinement) Stderr() io.ReadCloser { return started.stderr }
|
|
func (started *fakeStartedConfinement) Abort() error {
|
|
if started.child == nil || started.child.Process == nil {
|
|
return nil
|
|
}
|
|
_ = started.stdin.Close()
|
|
return started.child.Process.Kill()
|
|
}
|