package taskloop import ( "context" "encoding/json" "errors" "fmt" "io" "os" "path/filepath" "strings" "sync" "testing" "time" "iop/apps/agent/internal/projectlog" "iop/packages/go/agentconfig" "iop/packages/go/agentpolicy" clistatus "iop/packages/go/agentprovider/cli/status" "iop/packages/go/agentruntime" "iop/packages/go/agentstate" "iop/packages/go/agenttask" "iop/packages/go/agentworkspace" ) func TestRuntimePersistsManualLifecycleAndCompletesIndependentProject(t *testing.T) { fixture := newRuntimeFixture(t) ctx := context.Background() preview, err := fixture.runtime.Preview(ctx, "project-a") if err != nil { t.Fatalf("Preview(project-a): %v", err) } if preview.NextWork != "1" || len(preview.Blockers) != 0 { t.Fatalf("initial preview = %#v, want work 1", preview) } writeTaskFile(t, filepath.Join(fixture.projectA, "agent-task", "m-m1", "1_first", completeFileName), "complete\n") preview, err = fixture.runtime.Preview(ctx, "project-a") if err != nil { t.Fatalf("Preview(project-a) after predecessor completion: %v", err) } if preview.NextWork != "2" || len(preview.Blockers) != 0 { t.Fatalf("completed-predecessor preview = %#v, want work 2", preview) } started, err := fixture.runtime.StartProject(ctx, "project-a") if err != nil || started.Status != agenttask.ProjectStatusStarted { t.Fatalf("StartProject(project-a) = %#v, err = %v", started, err) } stopped, err := fixture.runtime.StopProject(ctx, "project-a") if err != nil || stopped.Status != agenttask.ProjectStatusStopped { t.Fatalf("StopProject(project-a) = %#v, err = %v", stopped, err) } resumed, err := fixture.runtime.ResumeProject(ctx, "project-a") if err != nil || resumed.Status != agenttask.ProjectStatusStarted { t.Fatalf("ResumeProject(project-a) = %#v, err = %v", resumed, err) } restarted, err := New(Config{ Snapshot: fixture.snapshot, Catalog: fixture.catalog, OwnerID: "taskloop-test-restart", Validator: DefaultValidator(), }) if err != nil { t.Fatalf("New(restart): %v", err) } restartedView, err := restarted.ProjectStatus(ctx, "project-a") if err != nil || restartedView.Status != agenttask.ProjectStatusStarted { t.Fatalf("restart status = %#v, err = %v", restartedView, err) } if _, err := restarted.StopProject(ctx, "project-a"); err != nil { t.Fatalf("stop restarted project-a: %v", err) } projectB, err := restarted.StartProject(ctx, "project-b") if err != nil || projectB.Status != agenttask.ProjectStatusStarted { t.Fatalf("StartProject(project-b) = %#v, err = %v", projectB, err) } if err := restarted.Reconcile(ctx); err != nil { t.Fatalf("Reconcile terminal project-b: %v", err) } projectB, err = restarted.ProjectStatus(ctx, "project-b") if err != nil || projectB.Status != agenttask.ProjectStatusCompleted { t.Fatalf("terminal project-b status = %#v, err = %v", projectB, err) } projectA, err := restarted.ProjectStatus(ctx, "project-a") if err != nil || projectA.Status != agenttask.ProjectStatusStopped { t.Fatalf("independent project-a status = %#v, err = %v", projectA, err) } } func TestRuntimeFakeProviderPersistedLifecycleRollbackAndRestart(t *testing.T) { fixture := newFakeProviderLifecycleFixture(t) ctx := context.Background() started, err := fixture.runtime.StartProject(ctx, "project") if err != nil || started.Status != agenttask.ProjectStatusStarted { t.Fatalf("StartProject = %#v, err = %v", started, err) } if err := fixture.runtime.Reconcile(ctx); err != nil { t.Fatalf("Reconcile: %v", err) } assertFakeProviderLifecycle(t, fixture) restarted, err := New(Config{ Snapshot: fixture.snapshot, Catalog: fixture.catalog, OwnerID: "taskloop-fake-provider-restart", Provider: fixture.provider, ReviewExecutor: fixture.reviewer, Validator: fixture.validator.Validate, }) if err != nil { t.Fatalf("New(restart): %v", err) } if err := restarted.Reconcile(ctx); err != nil { t.Fatalf("Reconcile(restart): %v", err) } if got := fixture.provider.DispatchCount(); got != 2 { t.Fatalf("dispatches after restart = %d, want 2", got) } if got := fixture.reviewer.Count(); got != 2 { t.Fatalf("reviews after restart = %d, want 2", got) } } func TestRuntimeRejectsUnknownAndUnselectedProjects(t *testing.T) { fixture := newRuntimeFixture(t) ctx := context.Background() if _, err := fixture.runtime.StartProject(ctx, "unknown"); err == nil { t.Fatal("unknown project start succeeded") } if err := fixture.runtime.selections.Save(ctx, "project-a", ""); err != nil { t.Fatalf("save corrupt selection precondition: %v", err) } if _, err := fixture.runtime.ProjectStatus(ctx, "project-a"); err == nil { t.Fatal("corrupt selection record was accepted") } } func TestConfigSelectorPersistsOrderedPolicyAndSuppliesQuotaBackedFailover(t *testing.T) { root := canonicalTempDir(t) workspace := filepath.Join(root, "workspace") if err := os.MkdirAll(workspace, 0o700); err != nil { t.Fatal(err) } global := `version: "1" defaults: default_profile: p1 selection: timezone: UTC default: provider: provider-a model: model-a profile: p1 rules: - id: quota-failover match: failure_codes: [quota_exhausted] target: provider: provider-a model: model-a profile: p1 - id: high-grade match: stages: [worker] min_grade: 7 max_grade: 10 target: provider: provider-b model: model-b profile: p2 isolation: default_mode: overlay ` local := fmt.Sprintf(`version: "1" device: state_root: %s overlay_root: %s log_root: %s projects: project-a: workspace: %s enabled: true `, filepath.Join(root, "state"), filepath.Join(root, "overlays"), filepath.Join(root, "logs"), workspace) snapshot, err := agentconfig.LoadRuntimeConfigBytes([]byte(global), []byte(local)) if err != nil { t.Fatalf("LoadRuntimeConfigBytes: %v", err) } providerCatalog := orderedTestCatalog() policySnapshot, err := selectionPolicySnapshot(snapshot, providerCatalog) if err != nil { t.Fatalf("selectionPolicySnapshot: %v", err) } state, err := agentstate.NewStore(filepath.Join(root, "state", "selector.json")) if err != nil { t.Fatalf("NewStore: %v", err) } now := time.Date(2026, 7, 31, 12, 0, 0, 0, time.UTC) selector := &configSelector{ snapshot: snapshot, policySnapshot: policySnapshot, catalog: providerCatalog, state: state, evaluator: agentpolicy.NewEvaluator(), now: func() time.Time { return now }, quota: staticQuotaObserver{}, } project := agenttask.ProjectRecord{ ProjectID: "project-a", WorkspaceID: WorkspaceIdentity(workspace), } work := agenttask.WorkRecord{ Unit: agenttask.WorkUnit{ ID: "work-1", Metadata: map[string]string{ "plan_path": "agent-task/m-m1/1/PLAN-cloud-G10.md", }, }, AttemptID: "attempt-1", } request := agenttask.SelectionRequest{Project: project, Work: work} selected, err := selector.Select(context.Background(), request) if err != nil { t.Fatalf("Select: %v", err) } if selected.ProviderID != "provider-b" || selected.ProfileID != "p2" { t.Fatalf("selected target = %#v", selected) } routePayload, _, found, err := state.LoadIntegrationRecord( context.Background(), routeDecisionKey(request), ) if err != nil || !found || len(routePayload) == 0 { t.Fatalf("persisted route found=%v size=%d err=%v", found, len(routePayload), err) } replayed, err := selector.Select(context.Background(), request) if err != nil || !sameExecutionTarget(replayed, selected) { t.Fatalf("replayed target = %#v, err = %v", replayed, err) } work.Target = &selected continuation, err := selector.ContinuationPolicy( context.Background(), agentruntimeContinuationRequest(t, project, work, selected), ) if err != nil { t.Fatalf("ContinuationPolicy: %v", err) } if len(continuation.Policy.FailoverCodes) != 1 || continuation.Policy.FailoverCodes[0] != "quota_exhausted" { t.Fatalf("failure policy = %#v", continuation.Policy) } var alternate *agenttask.FailureContinuationCandidate for index := range continuation.Candidates { if continuation.Candidates[index].Target.ProfileID == "p1" { alternate = &continuation.Candidates[index] } } if alternate == nil || !alternate.Eligible || alternate.Quota.State != agentpolicy.QuotaStateNotApplicable || alternate.Quota.Validity != agentpolicy.ObservationValid { t.Fatalf("alternate candidate = %#v", alternate) } currentQuota, err := staticQuotaObserver{}.ObserveQuota( context.Background(), selected, now, ) if err != nil { t.Fatal(err) } var commonCandidates []agentpolicy.ContinuationCandidate for _, candidate := range continuation.Candidates { commonCandidates = append( commonCandidates, agentpolicy.ContinuationCandidate{ Target: agentpolicy.TargetIdentity{ ProviderID: candidate.Target.ProviderID, ModelID: candidate.Target.ModelID, ProfileID: candidate.Target.ProfileID, ProfileRevision: candidate.Target.ProfileRevision, }, Eligible: candidate.Eligible, Quota: candidate.Quota, }, ) } decision, err := agentpolicy.DecideContinuation(agentpolicy.ContinuationRequest{ Policy: continuation.Policy, Current: agentpolicy.TargetIdentity{ ProviderID: selected.ProviderID, ModelID: selected.ModelID, ProfileID: selected.ProfileID, ProfileRevision: selected.ProfileRevision, }, Candidates: commonCandidates, Budget: agentpolicy.FailureBudget{Used: 1, Limit: 10}, Observation: agentpolicy.AttemptObservation{ ObservedAt: now, Quota: currentQuota, Failure: agentpolicy.FailureObservation{ Code: agentruntime.FailureCodeQuotaExhausted, }, }, }) if err != nil || decision.Action != agentpolicy.ContinuationFailover || decision.Target.ProfileID != "p1" { t.Fatalf("common continuation decision = %#v, err = %v", decision, err) } } func TestConfigSelectorContinuationUsesExactFailureRuleContext(t *testing.T) { root := canonicalTempDir(t) workspace := filepath.Join(root, "workspace") if err := os.MkdirAll(workspace, 0o700); err != nil { t.Fatal(err) } global := `version: "1" defaults: default_profile: p1 selection: timezone: UTC default: provider: provider-a model: model-a profile: p1 rules: - id: wrong-stage match: stages: [review] failure_codes: [process_exit] target: provider: provider-b model: model-b profile: p2 - id: wrong-grade match: min_grade: 11 failure_codes: [process_exit] target: provider: provider-b model: model-b profile: p2 - id: wrong-lane match: lanes: [local] failure_codes: [process_exit] target: provider: provider-b model: model-b profile: p2 - id: wrong-capability match: capabilities: [resume] failure_codes: [process_exit] target: provider: provider-b model: model-b profile: p2 - id: wrong-quota match: quota_states: [available] failure_codes: [process_exit] target: provider: provider-b model: model-b profile: p2 - id: quota-retry match: quota_states: [exhausted] stages: [worker] lanes: [cloud] min_grade: 7 max_grade: 10 capabilities: [run] failure_codes: [quota_exhausted] target: provider: provider-b model: model-b profile: p2 - id: process-failover match: quota_states: [exhausted] stages: [worker] lanes: [cloud] min_grade: 7 max_grade: 10 capabilities: [run] failure_codes: [process_exit] target: provider: provider-a model: model-a profile: p1 - id: ordinary-high-grade match: stages: [worker] min_grade: 7 max_grade: 10 target: provider: provider-b model: model-b profile: p2 isolation: default_mode: overlay ` local := fmt.Sprintf(`version: "1" device: state_root: %s overlay_root: %s log_root: %s projects: project-a: workspace: %s enabled: true `, filepath.Join(root, "state"), filepath.Join(root, "overlays"), filepath.Join(root, "logs"), workspace) snapshot, err := agentconfig.LoadRuntimeConfigBytes([]byte(global), []byte(local)) if err != nil { t.Fatalf("LoadRuntimeConfigBytes: %v", err) } providerCatalog := orderedTestCatalog() policySnapshot, err := selectionPolicySnapshot(snapshot, providerCatalog) if err != nil { t.Fatalf("selectionPolicySnapshot: %v", err) } now := time.Date(2026, 7, 31, 12, 0, 0, 0, time.UTC) selector := &configSelector{ snapshot: snapshot, policySnapshot: policySnapshot, catalog: providerCatalog, evaluator: agentpolicy.NewEvaluator(), now: func() time.Time { return now }, quota: staticQuotaObserver{}, } project := agenttask.ProjectRecord{ ProjectID: "project-a", WorkspaceID: WorkspaceIdentity(workspace), } baseWork := agenttask.WorkRecord{ Unit: agenttask.WorkUnit{ ID: "work-1", Metadata: map[string]string{ "plan_path": "agent-task/m-m1/1/PLAN-cloud-G10.md", "required_capabilities": "run", }, }, AttemptID: "attempt-1", } current, err := selector.executionTargetRef(agentconfig.TargetRef{ Provider: "provider-b", Model: "model-b", Profile: "p2", }) if err != nil { t.Fatalf("current target: %v", err) } tests := []struct { name string work agenttask.WorkRecord code agentruntime.FailureCode wantTarget string wantRetry bool wantError bool }{ { name: "process exit skips all nonmatching predicates", work: baseWork, code: agentruntime.FailureCodeProcessExit, wantTarget: "p1", }, { name: "quota failure uses only its matching current-target rule", work: baseWork, code: agentruntime.FailureCodeQuotaExhausted, wantTarget: "p2", wantRetry: true, }, { name: "default target cannot authorize continuation", work: func() agenttask.WorkRecord { work := baseWork work.Unit.Metadata = map[string]string{ "plan_path": "agent-task/m-m1/1/PLAN-cloud-G01.md", } return work }(), code: agentruntime.FailureCodeInternal, wantError: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { observation := continuationObservation( t, current, test.code, agentpolicy.QuotaStateExhausted, test.wantRetry, now, ) policy, err := selector.ContinuationPolicy( context.Background(), agenttask.FailureContinuationPolicyRequest{ Project: project, Work: test.work, CurrentTarget: current, Observation: observation, }, ) if test.wantError { if err == nil { t.Fatalf("ContinuationPolicy accepted non-failure/default rule: %#v", policy) } return } if err != nil { t.Fatalf("ContinuationPolicy: %v", err) } if len(policy.Candidates) != 1 || policy.Candidates[0].Target.ProfileID != test.wantTarget { t.Fatalf("continuation candidates = %#v", policy.Candidates) } if test.wantRetry { if len(policy.Policy.RetryableCodes) != 1 || policy.Policy.RetryableCodes[0] != test.code || len(policy.Policy.FailoverCodes) != 0 || policy.Candidates[0].Eligible { t.Fatalf("retry policy = %#v", policy) } } else if len(policy.Policy.FailoverCodes) != 1 || policy.Policy.FailoverCodes[0] != test.code || len(policy.Policy.RetryableCodes) != 0 || !policy.Candidates[0].Eligible { t.Fatalf("failover policy = %#v", policy) } }) } } func TestRuntimeArchivesTerminalProjectLogEvidence(t *testing.T) { fixture := newRuntimeFixture(t) projectID := agenttask.ProjectID("project-a") workspaceID := WorkspaceIdentity(fixture.projectA) workID := agenttask.WorkUnitID("archive-work") scoped, err := fixture.runtime.projectStores[string(projectID)].ForWorkUnit(workID) if err != nil { t.Fatalf("ForWorkUnit: %v", err) } if _, err := scoped.AppendRecord(context.Background(), projectlog.ProjectLogRecord{ SchemaVersion: projectlog.RecordSchemaVersion, RecordID: "terminal-record", LoopOrdinal: 1, DispatchOrdinal: 1, ProjectID: projectID, WorkspaceID: workspaceID, WorkUnitID: workID, AttemptID: "attempt-1", EventType: agenttask.EventCompleted, State: agenttask.WorkStateCompleted, StateRevision: "state-r1", Timestamp: time.Date(2026, 7, 31, 12, 0, 0, 0, time.UTC), Terminal: true, }); err != nil { t.Fatalf("AppendRecord: %v", err) } state := agenttask.ManagerState{ SchemaVersion: agenttask.StateSchemaVersion, Projects: map[agenttask.ProjectID]agenttask.ProjectRecord{ projectID: { ProjectID: projectID, WorkspaceID: workspaceID, Works: map[agenttask.WorkUnitID]agenttask.WorkRecord{ workID: { Unit: agenttask.WorkUnit{ID: workID}, State: agenttask.WorkStateCompleted, }, }, }, }, } if err := fixture.runtime.archiveTerminalProjectLogs( context.Background(), state, ); err != nil { t.Fatalf("archiveTerminalProjectLogs: %v", err) } var manifests int if err := filepath.Walk( fixture.snapshot.Config().Device.LogRoot, func(path string, info os.FileInfo, walkErr error) error { if walkErr != nil { return walkErr } if !info.IsDir() && strings.HasSuffix(path, ".manifest.json") { manifests++ } return nil }, ); err != nil { t.Fatalf("walk archives: %v", err) } if manifests != 1 { t.Fatalf("terminal archive manifests = %d, want 1", manifests) } } type staticQuotaObserver struct{} func (staticQuotaObserver) 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 } func continuationObservation( t *testing.T, target agenttask.ExecutionTarget, code agentruntime.FailureCode, state agentpolicy.QuotaState, retryable bool, observedAt time.Time, ) agentpolicy.AttemptObservation { t.Helper() var requiredCapabilities []string var usage *clistatus.UsageStatus switch state { case agentpolicy.QuotaStateAvailable: requiredCapabilities = []string{"overall"} usage = &clistatus.UsageStatus{DailyLimit: "50%"} case agentpolicy.QuotaStateExhausted: requiredCapabilities = []string{"overall"} usage = &clistatus.UsageStatus{DailyLimit: "0%"} case agentpolicy.QuotaStateUnknown: requiredCapabilities = []string{"overall"} usage = &clistatus.UsageStatus{DailyLimit: "not-a-percent"} case agentpolicy.QuotaStateNotApplicable: default: t.Fatalf("unsupported quota state %q", state) } quota := agentpolicy.NormalizeQuotaObservation( clistatus.NormalizeQuotaSnapshot( target.ProviderID, target.ProfileID, requiredCapabilities, observedAt, usage, nil, ), observedAt, time.Minute, ) if quota.State != state { t.Fatalf("normalized quota state = %q, want %q", quota.State, state) } return agentpolicy.AttemptObservation{ ObservedAt: observedAt, Quota: quota, Failure: agentpolicy.FailureObservation{ Code: code, Retryable: retryable, }, } } func agentruntimeContinuationRequest( t *testing.T, project agenttask.ProjectRecord, work agenttask.WorkRecord, target agenttask.ExecutionTarget, ) agenttask.FailureContinuationPolicyRequest { t.Helper() return agenttask.FailureContinuationPolicyRequest{ Project: project, Work: work, CurrentTarget: target, Observation: continuationObservation( t, target, agentruntime.FailureCodeQuotaExhausted, agentpolicy.QuotaStateExhausted, false, time.Date(2026, 7, 31, 12, 0, 0, 0, time.UTC), ), } } func orderedTestCatalog() agentconfig.Catalog { capabilities := []string{ "approval_bypass", "run", "unattended", "writable_root_confinement", } return agentconfig.Catalog{ Version: agentconfig.SchemaVersion, Providers: []agentconfig.Provider{ { ID: "provider-a", Command: "provider-a-must-not-run", VersionProbe: agentconfig.CommandProbe{Args: []string{"--version"}}, Authentication: agentconfig.AuthenticationProbe{ Args: []string{"auth", "status"}, }, Capabilities: capabilities, }, { ID: "provider-b", Command: "provider-b-must-not-run", VersionProbe: agentconfig.CommandProbe{Args: []string{"--version"}}, Authentication: agentconfig.AuthenticationProbe{ Args: []string{"auth", "status"}, }, Capabilities: capabilities, }, }, Models: []agentconfig.Model{ {ID: "model-a", Provider: "provider-a", Target: "model-a"}, {ID: "model-b", Provider: "provider-b", Target: "model-b"}, }, Profiles: []agentconfig.Profile{ { ID: "p1", Provider: "provider-a", Model: "model-a", Args: []string{"--model", "{{model}}"}, Capabilities: capabilities, }, { ID: "p2", Provider: "provider-b", Model: "model-b", Args: []string{"--model", "{{model}}"}, Capabilities: capabilities, }, }, } } type runtimeFixture struct { runtime *Runtime snapshot agentconfig.RuntimeSnapshot catalog agentconfig.Catalog projectA string projectB string } type fakeProviderLifecycleFixture struct { runtime *Runtime snapshot agentconfig.RuntimeSnapshot catalog agentconfig.Catalog provider *fakeLifecycleProvider reviewer *fakeLifecycleReviewer validator *fakeLifecycleValidator workspace string logRoot string } func newFakeProviderLifecycleFixture(t *testing.T) fakeProviderLifecycleFixture { t.Helper() root := canonicalTempDir(t) workspace := filepath.Join(root, "workspace") stateRoot := filepath.Join(root, "state") logRoot := filepath.Join(root, "logs") for _, directory := range []string{workspace, stateRoot, logRoot} { if err := os.MkdirAll(directory, 0o700); err != nil { t.Fatal(err) } } writeTaskFile(t, filepath.Join(workspace, "rejected.txt"), "base rejected\n") writeTaskFile(t, filepath.Join(workspace, "sibling.txt"), "base sibling\n") createLifecycleTaskPair(t, workspace, "1_rejected", "rejected.txt") createLifecycleTaskPair(t, workspace, "2_sibling", "sibling.txt") runIntegrationGit(t, workspace, "init", "-q") runIntegrationGit(t, workspace, "config", "user.email", "taskloop@example.invalid") runIntegrationGit(t, workspace, "config", "user.name", "Taskloop Fixture") runIntegrationGit(t, workspace, "add", "-A") runIntegrationGit(t, workspace, "commit", "-q", "-m", "base") global := `version: "1" defaults: default_profile: p1 selection: timezone: UTC default: provider: test-provider model: test-model profile: p1 isolation: default_mode: overlay retention: completed_days: 14 blocked_days: 14 ` local := fmt.Sprintf(`version: "1" device: state_root: %s overlay_root: %s log_root: %s projects: project: workspace: %s enabled: true selected_milestone: m1 `, stateRoot, filepath.Join(root, "overlays"), logRoot, workspace) snapshot, err := agentconfig.LoadRuntimeConfigBytes([]byte(global), []byte(local)) if err != nil { t.Fatalf("LoadRuntimeConfigBytes: %v", err) } provider := &fakeLifecycleProvider{} reviewer := &fakeLifecycleReviewer{} validator := &fakeLifecycleValidator{} providerCatalog := testCatalog() runtime, err := New(Config{ Snapshot: snapshot, Catalog: providerCatalog, OwnerID: "taskloop-fake-provider", Provider: provider, ReviewExecutor: reviewer, Validator: validator.Validate, }) if err != nil { t.Fatalf("New: %v", err) } return fakeProviderLifecycleFixture{ runtime: runtime, snapshot: snapshot, catalog: providerCatalog, provider: provider, reviewer: reviewer, validator: validator, workspace: workspace, logRoot: logRoot, } } func createLifecycleTaskPair( t *testing.T, workspace, taskDirectory, target string, ) { t.Helper() taskRoot := filepath.Join(workspace, "agent-task", "m-m1", taskDirectory) if err := os.MkdirAll(taskRoot, 0o700); err != nil { t.Fatal(err) } reviewRelative := filepath.ToSlash( filepath.Join("agent-task", "m-m1", taskDirectory, "CODE_REVIEW-test.md"), ) writeTaskFile(t, filepath.Join(taskRoot, "PLAN-test.md"), fmt.Sprintf(`# Plan ## Modified Files Summary | File | Purpose | |------|---------| | %s | Exercise provider output. | | %s | Record implementation and canonical review. | `, "`"+target+"`", "`"+reviewRelative+"`")) writeTaskFile(t, filepath.Join(taskRoot, "CODE_REVIEW-test.md"), `# Code Review Reference ## Implementation Notes Deterministic fake-provider implementation evidence is complete. `) } func assertFakeProviderLifecycle( t *testing.T, fixture fakeProviderLifecycleFixture, ) { t.Helper() if got := fixture.provider.DispatchCount(); got != 2 { t.Fatalf("dispatches = %d, want 2", got) } if got := fixture.reviewer.Count(); got != 2 { t.Fatalf("reviews = %d, want 2", got) } if got := fixture.validator.Count(); got != 2 { t.Fatalf("validator calls = %d, want 2", got) } state, _, err := fixture.runtime.StateStore().Load(context.Background()) if err != nil { t.Fatal(err) } project := state.Projects["project"] rejected := project.Works["1"] if rejected.State != agenttask.WorkStateTerminalDeferred || rejected.Integration == nil || rejected.Integration.Outcome != agenttask.IntegrationOutcomeTerminalDeferred || rejected.Blocker == nil || !strings.Contains(rejected.Blocker.Message, "validation") || rejected.ChangeSet == nil { t.Fatalf("rejected work = %#v", rejected) } sibling := project.Works["2"] if sibling.State != agenttask.WorkStateCompleted || sibling.Integration == nil || sibling.Integration.Outcome != agenttask.IntegrationOutcomeIntegrated || !sibling.CompletionVerified { t.Fatalf("sibling work = %#v", sibling) } assertIntegrationFile(t, filepath.Join(fixture.workspace, "rejected.txt"), "base rejected\n") assertIntegrationFile(t, filepath.Join(fixture.workspace, "sibling.txt"), "integrated 2\n") var archiveManifests int terminalTimelines := make(map[agenttask.WorkUnitID]bool) if err := filepath.Walk( fixture.logRoot, func(path string, info os.FileInfo, walkErr error) error { if walkErr != nil { return walkErr } if !info.IsDir() && strings.HasSuffix(path, ".manifest.json") { archiveManifests++ } if !info.IsDir() && strings.HasSuffix(path, ".timeline.jsonl") { content, err := os.ReadFile(path) if err != nil { return err } lines := strings.Split(strings.TrimSpace(string(content)), "\n") var prior uint64 var last projectlog.ProjectLogRecord for index, line := range lines { var record projectlog.ProjectLogRecord if err := json.Unmarshal([]byte(line), &record); err != nil { return err } if index > 0 && record.Sequence != prior+1 { return fmt.Errorf("project log sequence %d does not follow %d", record.Sequence, prior) } prior = record.Sequence last = record } if !last.Terminal { return errors.New("project log timeline is not terminal") } terminalTimelines[last.WorkUnitID] = true } return nil }, ); err != nil { t.Fatal(err) } if archiveManifests != 2 { t.Fatalf("terminal archive manifests = %d, want 2", archiveManifests) } if !terminalTimelines["1"] || !terminalTimelines["2"] { t.Fatalf("terminal timeline work units = %#v", terminalTimelines) } } type fakeLifecycleProvider struct { mu sync.Mutex requests []agenttask.DispatchRequest } func (provider *fakeLifecycleProvider) Prepare( _ context.Context, request agenttask.DispatchRequest, ) (agenttask.ProviderLaunch, error) { if request.Confinement == nil || request.Permit == nil { return nil, errors.New("fake provider requires admitted confinement") } binding := request.Confinement.Binding() if err := request.Confinement.Validate(binding); err != nil { return nil, err } provider.mu.Lock() provider.requests = append(provider.requests, request) ordinal := len(provider.requests) provider.mu.Unlock() return &fakeLifecycleLaunch{ request: request, root: binding.WorkingDir, ordinal: ordinal, }, nil } func (provider *fakeLifecycleProvider) DispatchCount() int { provider.mu.Lock() defer provider.mu.Unlock() return len(provider.requests) } type fakeLifecycleLaunch struct { request agenttask.DispatchRequest root string ordinal int } func (launch *fakeLifecycleLaunch) Command() agenttask.ConfinementCommand { return agenttask.ConfinementCommand{Name: "true"} } func (launch *fakeLifecycleLaunch) BindStarted( started agenttask.StartedConfinement, ) (agenttask.ProviderInvocation, error) { if started == nil || started.Child() == nil || started.Child().Process == nil { return nil, errors.New("fake provider child is incomplete") } opaque := fmt.Sprintf("fake-process-%d", started.Child().Process.Pid) return &fakeLifecycleInvocation{ request: launch.request, root: launch.root, started: started, locators: []agenttask.LocatorRecord{{ Kind: agenttask.LocatorProcess, Opaque: opaque, Revision: digestStrings("fake-process", opaque), ProjectID: launch.request.Project.ProjectID, WorkspaceID: launch.request.Project.WorkspaceID, WorkUnitID: launch.request.Work.Unit.ID, AttemptID: launch.request.Work.AttemptID, }}, }, nil } type fakeLifecycleInvocation struct { request agenttask.DispatchRequest root string started agenttask.StartedConfinement locators []agenttask.LocatorRecord } func (invocation *fakeLifecycleInvocation) Locators() []agenttask.LocatorRecord { return append([]agenttask.LocatorRecord(nil), invocation.locators...) } func (invocation *fakeLifecycleInvocation) Wait( context.Context, ) (agenttask.Submission, error) { if err := waitFakeLifecycleChild(invocation.started); err != nil { return agenttask.Submission{}, err } target := "" for _, candidate := range invocation.request.Work.Unit.DeclaredWriteSet { if !strings.HasPrefix(candidate, "agent-task/") { target = candidate break } } if target == "" { return agenttask.Submission{}, errors.New("fake provider target is missing") } content := "integrated " + string(invocation.request.Work.Unit.ID) + "\n" if err := os.WriteFile( filepath.Join(invocation.root, filepath.FromSlash(target)), []byte(content), 0o600, ); err != nil { return agenttask.Submission{}, err } return agenttask.Submission{ ProjectID: invocation.request.Project.ProjectID, WorkUnitID: invocation.request.Work.Unit.ID, AttemptID: invocation.request.Work.AttemptID, ArtifactID: artifactIdentity(invocation.request.Work), Ready: true, Locators: invocation.Locators(), }, nil } func (invocation *fakeLifecycleInvocation) Cancel(context.Context) error { return invocation.started.Abort() } func waitFakeLifecycleChild(started agenttask.StartedConfinement) error { if stdin := started.Stdin(); stdin != nil { _ = stdin.Close() } if stdout := started.Stdout(); stdout != nil { _, _ = io.Copy(io.Discard, stdout) _ = stdout.Close() } if stderr := started.Stderr(); stderr != nil { _, _ = io.Copy(io.Discard, stderr) _ = stderr.Close() } return started.Child().Wait() } type fakeLifecycleReviewer struct { mu sync.Mutex count int } func (reviewer *fakeLifecycleReviewer) ExecuteReview( _ context.Context, _ agenttask.ReviewRequest, root, _, reviewRelative string, ) error { reviewer.mu.Lock() reviewer.count++ reviewer.mu.Unlock() path := filepath.Join(root, filepath.FromSlash(reviewRelative)) file, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0) if err != nil { return err } defer file.Close() _, err = io.WriteString( file, "\n## Code Review Result\n\nOverall Verdict: PASS\n", ) return err } func (reviewer *fakeLifecycleReviewer) Count() int { reviewer.mu.Lock() defer reviewer.mu.Unlock() return reviewer.count } type fakeLifecycleValidator struct { mu sync.Mutex count int } func (validator *fakeLifecycleValidator) Validate( _ context.Context, request agentworkspace.ValidationRequest, ) error { validator.mu.Lock() validator.count++ validator.mu.Unlock() content, err := os.ReadFile(filepath.Join(request.ValidationRoot, "rejected.txt")) if err != nil { return err } if string(content) != "base rejected\n" { return errors.New("fake validator rejected project-1 candidate") } return nil } func (validator *fakeLifecycleValidator) Count() int { validator.mu.Lock() defer validator.mu.Unlock() return validator.count } type staticArtifactRoot string func (root staticArtifactRoot) ArtifactRoot(agenttask.WorkRecord) (string, error) { return string(root), nil } func newRuntimeFixture(t *testing.T) runtimeFixture { t.Helper() root := canonicalTempDir(t) stateRoot := filepath.Join(root, "state") projectA := filepath.Join(root, "project-a") projectB := filepath.Join(root, "project-b") if err := os.MkdirAll(stateRoot, 0700); err != nil { t.Fatalf("create state root: %v", err) } createTaskPair(t, projectA, "m1", "1_first", false) createTaskPair(t, projectA, "m1", "2+1_second", false) createTaskPair(t, projectB, "m1", "1_terminal", true) global := `version: "1" defaults: default_profile: p1 auto_resume_interrupted: true selection: timezone: UTC default: provider: test-provider model: test-model profile: p1 isolation: default_mode: overlay retention: completed_days: 14 ` local := fmt.Sprintf(`version: "1" device: state_root: %s overlay_root: %s log_root: %s projects: project-a: workspace: %s enabled: true selected_milestone: m1 project-b: workspace: %s enabled: true selected_milestone: m1 `, stateRoot, filepath.Join(root, "overlays"), filepath.Join(root, "logs"), projectA, projectB) snapshot, err := agentconfig.LoadRuntimeConfigBytes([]byte(global), []byte(local)) if err != nil { t.Fatalf("LoadRuntimeConfigBytes: %v", err) } catalog := testCatalog() runtime, err := New(Config{ Snapshot: snapshot, Catalog: catalog, OwnerID: "taskloop-test", Validator: DefaultValidator(), }) if err != nil { t.Fatalf("New task runtime: %v", err) } return runtimeFixture{ runtime: runtime, snapshot: snapshot, catalog: catalog, projectA: projectA, projectB: projectB, } } func canonicalTempDir(t *testing.T) string { t.Helper() root, err := filepath.EvalSymlinks(t.TempDir()) if err != nil { t.Fatalf("resolve canonical temporary directory: %v", err) } return root } func testCatalog() agentconfig.Catalog { capabilities := []string{ "approval_bypass", "run", "unattended", "writable_root_confinement", } return agentconfig.Catalog{ Version: agentconfig.SchemaVersion, Providers: []agentconfig.Provider{{ ID: "test-provider", Command: "provider-must-not-run-in-tests", VersionProbe: agentconfig.CommandProbe{Args: []string{"--version"}}, Authentication: agentconfig.AuthenticationProbe{Args: []string{"auth", "status"}}, Capabilities: capabilities, }}, Models: []agentconfig.Model{{ ID: "test-model", Provider: "test-provider", Target: "test-model", }}, Profiles: []agentconfig.Profile{{ ID: "p1", Provider: "test-provider", Model: "test-model", Args: []string{"--model", "{{model}}"}, Capabilities: capabilities, }}, } } func createTaskPair(t *testing.T, workspace, milestone, task string, complete bool) { t.Helper() taskRoot := filepath.Join(workspace, "agent-task", "m-"+milestone, task) if err := os.MkdirAll(taskRoot, 0700); err != nil { t.Fatalf("create task %s: %v", task, err) } writeTaskFile(t, filepath.Join(taskRoot, "PLAN-test.md"), `# Plan ## Modified Files Summary | File | Purpose | |------|---------| | `+"`README.md`"+` | Exercise the task runtime. | `) writeTaskFile(t, filepath.Join(taskRoot, "CODE_REVIEW-test.md"), `# Code Review Reference ## Implementation Notes Implementation evidence is complete. `) if complete { writeTaskFile(t, filepath.Join(taskRoot, completeFileName), "complete\n") } } func writeTaskFile(t *testing.T, path, content string) { t.Helper() if err := os.WriteFile(path, []byte(content), 0600); err != nil { t.Fatalf("write %s: %v", path, err) } }