package taskloop import ( "context" "encoding/json" "errors" "io" "os" "os/exec" "path/filepath" "reflect" "strings" "testing" "iop/packages/go/agentconfig" "iop/packages/go/agentguard" "iop/packages/go/agenttask" "iop/packages/go/agentworkspace" ) func TestReviewerRejectsPrematureAndIdentityMismatchedReview(t *testing.T) { fixture := newRuntimeFixture(t) reviewer, err := NewReviewer( fixture.snapshot, &agentworkspace.Backend{}, nil, ) if err != nil { t.Fatalf("NewReviewer: %v", err) } project := agenttask.ProjectRecord{ ProjectID: "project-a", WorkspaceID: WorkspaceIdentity(fixture.projectA), } submission := agenttask.Submission{ ProjectID: project.ProjectID, WorkUnitID: "1", AttemptID: "attempt-1", ArtifactID: "artifact-1", Ready: true, } work := agenttask.WorkRecord{ Unit: agenttask.WorkUnit{ ID: "1", Metadata: map[string]string{ "plan_path": "agent-task/m-m1/1_first/PLAN-test.md", "review_path": "agent-task/m-m1/1_first/CODE_REVIEW-test.md", }, }, AttemptID: "attempt-1", } if _, err := reviewer.Review(context.Background(), agenttask.ReviewRequest{ Project: project, Work: work, Submission: submission, }); err == nil || !strings.Contains(err.Error(), "identity mismatch") { t.Fatalf("missing durable submission error = %v", err) } mismatched := submission mismatched.ArtifactID = "artifact-2" work.Submission = &submission if _, err := reviewer.Review(context.Background(), agenttask.ReviewRequest{ Project: project, Work: work, Submission: mismatched, }); err == nil || !strings.Contains(err.Error(), "identity mismatch") { t.Fatalf("mismatched submission error = %v", err) } if _, err := reviewer.Review(context.Background(), agenttask.ReviewRequest{ Project: project, Work: work, Submission: submission, }); err == nil || !strings.Contains(err.Error(), "no retained isolation") { t.Fatalf("premature review error = %v", err) } } func TestReadReviewVerdictAcceptsCanonicalOverallVerdict(t *testing.T) { fixture := newRuntimeFixture(t) path := fixture.projectA + "/agent-task/m-m1/1_first/CODE_REVIEW-test.md" writeTaskFile(t, path, `# Code Review Reference ## Code Review Result Overall Verdict: PASS `) verdict, message, err := readReviewVerdict(path) if err != nil || verdict != agenttask.ReviewVerdictPass || message != "official review passed" { t.Fatalf("readReviewVerdict = %s/%q, err = %v", verdict, message, err) } } func TestOfficialReviewPromptPreservesRetainedArtifacts(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 := officialReviewPrompt(plan, review) for _, required := range []string{ plan, review, "bounded official-review step", "not task finalization", "Do not modify review-only checklists", "archive, move, rename, or delete either active artifact", "Append only one `## Code Review Result` section", } { if !strings.Contains(prompt, required) { t.Fatalf("official review prompt does not contain %q: %q", required, prompt) } } } func TestReadReviewVerdictRejectsLegacyDuplicateAndBareValues(t *testing.T) { fixture := newRuntimeFixture(t) path := fixture.projectA + "/agent-task/m-m1/1_first/CODE_REVIEW-test.md" for name, result := range map[string]string{ "legacy": "Verdict: PASS", "bare": "PASS", "duplicate": "Overall Verdict: PASS\nOverall Verdict: FAIL", "invalid": "Overall Verdict: pass", } { t.Run(name, func(t *testing.T) { writeTaskFile(t, path, "# Review\n\n## Code Review Result\n\n"+result+"\n") if _, _, err := readReviewVerdict(path); err == nil { t.Fatalf("result %q was accepted", result) } }) } } func TestCatalogReviewExecutorRequiresExactRetainedConfinement(t *testing.T) { fixture := newRetainedExecutionFixture(t, testCatalog(), "p1") reviewPath := filepath.Join( fixture.descriptor.WorkingDir, filepath.FromSlash(fixture.work.Unit.Metadata["review_path"]), ) writeTaskFile(t, reviewPath, "# Code Review Reference\n") fixture.confinement.launch = func( ctx context.Context, command agenttask.ConfinementCommand, ) (agenttask.StartedConfinement, error) { if command.Name != "provider-must-not-run-in-tests" { return nil, errors.New("unexpected provider command") } return startFixtureCommand( ctx, "printf '\\n## Code Review Result\\n\\nOverall Verdict: PASS\\n' >> \"$1\"", reviewPath, ) } executor, err := NewCatalogReviewExecutor(testCatalog(), fixture.backend) if err != nil { t.Fatalf("NewCatalogReviewExecutor: %v", err) } request := agenttask.ReviewRequest{ Project: fixture.project, Work: fixture.work, Submission: agenttask.Submission{ ProjectID: fixture.project.ProjectID, WorkUnitID: fixture.work.Unit.ID, AttemptID: fixture.work.AttemptID, ArtifactID: artifactIdentity(fixture.work), Ready: true, }, IdempotencyKey: "review-fixture", } if err := executor.ExecuteReview( context.Background(), request, fixture.descriptor.WorkingDir, fixture.work.Unit.Metadata["plan_path"], fixture.work.Unit.Metadata["review_path"], ); err != nil { t.Fatalf("ExecuteReview: %v", err) } if fixture.confinement.starts != 1 { t.Fatalf("confined starts = %d, want 1", fixture.confinement.starts) } if verdict, _, err := readReviewVerdict(reviewPath); err != nil || verdict != agenttask.ReviewVerdictPass { t.Fatalf("review result = %s, err = %v", verdict, err) } fixture.confinement.binding.ProfileRevision = "stale-profile" if err := executor.ExecuteReview( context.Background(), request, fixture.descriptor.WorkingDir, fixture.work.Unit.Metadata["plan_path"], fixture.work.Unit.Metadata["review_path"], ); err == nil { t.Fatal("stale confinement was accepted") } if fixture.confinement.starts != 1 { t.Fatalf("stale proof launched a process; starts = %d", fixture.confinement.starts) } } type retainedExecutionFixture struct { project agenttask.ProjectRecord work agenttask.WorkRecord record agentworkspace.OverlayRecord descriptor agentguard.IsolationDescriptor confinement *recordingConfinement backend *fakeRetainedBackend } func newRetainedExecutionFixture( t *testing.T, providerCatalog agentconfig.Catalog, profileID string, ) retainedExecutionFixture { t.Helper() resolved, ok := providerCatalog.ResolveProfile(profileID) if !ok { t.Fatalf("profile %q is missing", profileID) } root := canonicalTempDir(t) taskRoot := filepath.Join(root, "retained") viewRoot := filepath.Join(taskRoot, "view") tempRoot := filepath.Join(taskRoot, "tmp") cacheRoot := filepath.Join(taskRoot, "cache") snapshotRoot := filepath.Join(root, "snapshot") canonicalRoot := filepath.Join(root, "canonical") for _, directory := range []string{ taskRoot, viewRoot, tempRoot, cacheRoot, snapshotRoot, canonicalRoot, } { if err := mkdirOwned(directory); err != nil { t.Fatal(err) } } workspaceID := WorkspaceIdentity(canonicalRoot) project := agenttask.ProjectRecord{ ProjectID: "project-a", WorkspaceID: workspaceID, Intent: &agenttask.StartIntent{ ProjectID: "project-a", WorkspaceID: workspaceID, ConfigRevision: "config-r1", GrantRevision: "grant-r1", }, } target := agenttask.ExecutionTarget{ ProviderID: resolved.Provider.ID, ModelID: resolved.Model.ID, ProfileID: resolved.Profile.ID, ProfileRevision: guardProfile(resolved).Revision, ConfigRevision: "config-r1", Capacity: 1, } work := agenttask.WorkRecord{ Unit: agenttask.WorkUnit{ ID: "work-1", IsolationMode: agentguard.IsolationModeOverlay, 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", DispatchOrdinal: 1, Target: &target, } record := agentworkspace.OverlayRecord{ SchemaVersion: 2, IsolationID: "overlay:fixture", Revision: "overlay-r1", IdempotencyKey: "dispatch/fixture/isolation", ProjectID: string(project.ProjectID), WorkspaceID: string(project.WorkspaceID), WorkUnitID: string(work.Unit.ID), AttemptID: string(work.AttemptID), CanonicalRoot: canonicalRoot, ConfigRevision: string(target.ConfigRevision), GrantRevision: "grant-r1", ProfileRevision: target.ProfileRevision, SnapshotRevision: "snapshot-r1", ConfinementRevision: "confinement-r1", Mode: agentguard.IsolationModeOverlay, Locator: agentworkspace.OverlayLocator{ SnapshotRoot: snapshotRoot, ViewRoot: viewRoot, TempRoot: tempRoot, CacheRoot: cacheRoot, OverlayRecord: filepath.Join(taskRoot, "overlay.json"), }, Retention: agentworkspace.RetentionStateActive, } descriptor := agentguard.IsolationDescriptor{ ID: record.IsolationID, Revision: record.Revision, Mode: record.Mode, BaseRoot: record.CanonicalRoot, TaskRoot: taskRoot, WorkingDir: viewRoot, WritableRoots: []string{viewRoot, tempRoot, cacheRoot}, PinnedBaseRevision: record.SnapshotRevision, ConfinementRevision: record.ConfinementRevision, } work.Isolation = &agenttask.IsolationIdentity{ ID: descriptor.ID, Revision: descriptor.Revision, Mode: descriptor.Mode, PinnedBaseRevision: descriptor.PinnedBaseRevision, TaskRoot: descriptor.TaskRoot, } binding := agenttask.ConfinementBinding{ Revision: record.ConfinementRevision, IsolationID: record.IsolationID, IsolationRevision: record.Revision, PinnedBaseRevision: record.SnapshotRevision, ConfigRevision: record.ConfigRevision, GrantRevision: record.GrantRevision, ProfileRevision: record.ProfileRevision, BaseRoot: record.CanonicalRoot, RuntimeRoot: root, SnapshotRoot: record.Locator.SnapshotRoot, TaskRoot: taskRoot, WorkingDir: viewRoot, WritableRoots: append([]string(nil), descriptor.WritableRoots...), } confinement := &recordingConfinement{binding: binding} backend := &fakeRetainedBackend{ record: record, prepared: agenttask.PreparedIsolation{ Descriptor: &descriptor, Profile: guardProfile(resolved), Confinement: confinement, }, } content, err := json.Marshal(record) if err != nil { t.Fatal(err) } writeTaskFile(t, record.Locator.OverlayRecord, string(content)) for _, relative := range []string{ work.Unit.Metadata["plan_path"], work.Unit.Metadata["review_path"], } { path := filepath.Join(viewRoot, filepath.FromSlash(relative)) if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { t.Fatal(err) } writeTaskFile(t, path, "# Fixture\n") } return retainedExecutionFixture{ project: project, work: work, record: record, descriptor: descriptor, confinement: confinement, backend: backend, } } func mkdirOwned(path string) error { return os.MkdirAll(path, 0o700) } type fakeRetainedBackend struct { record agentworkspace.OverlayRecord prepared agenttask.PreparedIsolation prepareErr error prepares int } func (backend *fakeRetainedBackend) LoadRecord( descriptor agentguard.IsolationDescriptor, ) (agentworkspace.OverlayRecord, error) { if backend.prepared.Descriptor == nil || !reflect.DeepEqual(descriptor, *backend.prepared.Descriptor) { return agentworkspace.OverlayRecord{}, errors.New("fixture descriptor mismatch") } return backend.record, nil } func (backend *fakeRetainedBackend) Prepare( _ context.Context, request agenttask.IsolationRequest, ) (agenttask.PreparedIsolation, error) { backend.prepares++ if backend.prepareErr != nil { return agenttask.PreparedIsolation{}, backend.prepareErr } if request.IdempotencyKey != backend.record.IdempotencyKey || string(request.Project.ProjectID) != backend.record.ProjectID || string(request.Work.Unit.ID) != backend.record.WorkUnitID || request.Target.ProfileRevision != backend.record.ProfileRevision { return agenttask.PreparedIsolation{}, errors.New("fixture prepare identity mismatch") } return backend.prepared, nil } type recordingConfinement struct { binding agenttask.ConfinementBinding starts int command agenttask.ConfinementCommand launch func( context.Context, agenttask.ConfinementCommand, ) (agenttask.StartedConfinement, error) } func (confinement *recordingConfinement) Revision() string { return confinement.binding.Revision } func (confinement *recordingConfinement) Binding() agenttask.ConfinementBinding { binding := confinement.binding binding.WritableRoots = append([]string(nil), binding.WritableRoots...) return binding } func (confinement *recordingConfinement) Validate( expected agenttask.ConfinementBinding, ) error { if !reflect.DeepEqual(confinement.binding, expected) { return errors.New("fixture confinement identity mismatch") } return nil } func (confinement *recordingConfinement) Start( ctx context.Context, command agenttask.ConfinementCommand, ) (agenttask.StartedConfinement, error) { confinement.starts++ confinement.command = command if confinement.launch == nil { return nil, errors.New("fixture launch is not configured") } return confinement.launch(ctx, command) } func startFixtureCommand( ctx context.Context, script string, arguments ...string, ) (agenttask.StartedConfinement, error) { args := append([]string{"-c", script, "fixture"}, arguments...) child := exec.CommandContext(ctx, "/bin/sh", args...) stdin, err := child.StdinPipe() if err != nil { return nil, err } stdout, err := child.StdoutPipe() if err != nil { return nil, err } stderr, err := child.StderrPipe() if err != nil { return nil, err } if err := child.Start(); err != nil { return nil, err } return &fixtureStarted{ child: child, stdin: stdin, stdout: stdout, stderr: stderr, }, nil } type fixtureStarted struct { child *exec.Cmd stdin io.WriteCloser stdout io.ReadCloser stderr io.ReadCloser } func (started *fixtureStarted) Child() *exec.Cmd { return started.child } func (started *fixtureStarted) Stdin() io.WriteCloser { return started.stdin } func (started *fixtureStarted) Stdout() io.ReadCloser { return started.stdout } func (started *fixtureStarted) Stderr() io.ReadCloser { return started.stderr } func (started *fixtureStarted) Abort() error { return started.child.Process.Kill() }