iop/apps/agent/internal/taskloop/cutover_test.go

209 lines
5.8 KiB
Go

package taskloop
import (
"bufio"
"fmt"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"testing"
)
const goPlanValidatorCommand = "go run ./apps/agent/cmd/agent task-loop validate-plan --workspace <workspace> <candidate-plan>"
var cutoverStaticOwnershipDocuments = []string{
"agent-ops/skills/common/plan/SKILL.md",
"agent-ops/skills/common/code-review/SKILL.md",
"agent-ops/skills/project/orchestrate-agent-task-loop/SKILL.md",
"agent-ops/rules/project/domain/testing/rules.md",
"agent-ops/rules/project/rules.md",
}
var cutoverProductionRoots = []string{
"agent-ops/skills/common",
"agent-ops/skills/project",
"agent-ops/rules",
}
func TestCutoverProductionOwnershipHasNoReferenceCallerOrStaticRouteTable(t *testing.T) {
root := repoRoot(t)
if err := validateCutoverProductionOwnership(root); err != nil {
t.Fatal(err)
}
}
func TestCutoverProductionOwnershipRejectsInjectedPythonCaller(t *testing.T) {
root := t.TempDir()
for _, relativeRoot := range cutoverProductionRoots {
if err := os.MkdirAll(filepath.Join(root, relativeRoot), 0700); err != nil {
t.Fatal(err)
}
}
for _, relative := range cutoverStaticOwnershipDocuments {
contents := ""
if relative == "agent-ops/skills/common/plan/SKILL.md" || relative == "agent-ops/skills/common/code-review/SKILL.md" {
contents = goPlanValidatorCommand
}
path := filepath.Join(root, relative)
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, []byte(contents), 0600); err != nil {
t.Fatal(err)
}
}
relative := "agent-ops/skills/common/other/SKILL.md"
path := filepath.Join(root, relative)
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, []byte("python3 dispatch.py --validate-plan candidate\n"), 0600); err != nil {
t.Fatal(err)
}
err := validateCutoverProductionOwnership(root)
if err == nil || !strings.Contains(err.Error(), "matches") || !strings.Contains(err.Error(), ":1") {
t.Fatalf("validateCutoverProductionOwnership error = %v", err)
}
}
func TestCutoverNodeRemainsSharedRuntimeConsumer(t *testing.T) {
root := repoRoot(t)
patterns := []*regexp.Regexp{
regexp.MustCompile(`agenttask\.NewManager\(`),
regexp.MustCompile(`taskloop\.New\(`),
regexp.MustCompile(`NewProvider\(`),
}
err := filepath.WalkDir(filepath.Join(root, "apps", "node"), func(path string, entry os.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") || strings.HasSuffix(entry.Name(), "_test.go") {
return nil
}
assertNoPatterns(t, path, patterns)
return nil
})
if err != nil {
t.Fatal(err)
}
}
func assertNoPatterns(t *testing.T, path string, patterns []*regexp.Regexp) {
t.Helper()
file, err := os.Open(path)
if err != nil {
t.Fatal(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
line := 0
for scanner.Scan() {
line++
for _, pattern := range patterns {
if pattern.MatchString(scanner.Text()) {
t.Fatalf("cutover ownership violation %s:%d matches %q", path, line, pattern.String())
}
}
}
if err := scanner.Err(); err != nil {
t.Fatal(err)
}
}
func validateCutoverProductionOwnership(root string) error {
callerPatterns := []*regexp.Regexp{
regexp.MustCompile(`dispatch\.py`),
regexp.MustCompile(`python3 .*orchestrate-agent-task-loop`),
}
staticOwnershipPatterns := []*regexp.Regexp{
regexp.MustCompile(`gpt-5\.6`),
regexp.MustCompile(`claude-opus`),
regexp.MustCompile(`Gemini`),
regexp.MustCompile(`ornith`),
regexp.MustCompile(`laguna`),
}
documents, err := discoverCutoverProductionDocuments(root)
if err != nil {
return err
}
for _, relative := range documents {
path := filepath.Join(root, relative)
if err := noPatternViolation(path, callerPatterns); err != nil {
return err
}
}
for _, relative := range cutoverStaticOwnershipDocuments {
path := filepath.Join(root, relative)
if err := noPatternViolation(path, staticOwnershipPatterns); err != nil {
return err
}
if relative == "agent-ops/skills/common/plan/SKILL.md" || relative == "agent-ops/skills/common/code-review/SKILL.md" {
contents, err := os.ReadFile(path)
if err != nil {
return err
}
if !strings.Contains(string(contents), goPlanValidatorCommand) {
return fmt.Errorf("cutover ownership violation %s lacks %q", path, goPlanValidatorCommand)
}
}
}
return nil
}
func discoverCutoverProductionDocuments(root string) ([]string, error) {
var documents []string
for _, relativeRoot := range cutoverProductionRoots {
directory := filepath.Join(root, filepath.FromSlash(relativeRoot))
err := filepath.WalkDir(directory, func(path string, entry os.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if entry.IsDir() {
relative, err := filepath.Rel(root, path)
if err != nil {
return err
}
relative = filepath.ToSlash(relative)
if entry.Name() == "tests" || strings.HasPrefix(entry.Name(), ".") || relative == "agent-ops/skills/project/orchestrate-agent-task-loop/scripts" {
return filepath.SkipDir
}
return nil
}
if !entry.Type().IsRegular() {
return nil
}
relative, err := filepath.Rel(root, path)
if err != nil {
return err
}
documents = append(documents, filepath.ToSlash(relative))
return nil
})
if err != nil {
return nil, err
}
}
sort.Strings(documents)
return documents, nil
}
func noPatternViolation(path string, patterns []*regexp.Regexp) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
scanner := bufio.NewScanner(file)
line := 0
for scanner.Scan() {
line++
for _, pattern := range patterns {
if pattern.MatchString(scanner.Text()) {
return fmt.Errorf("cutover ownership violation %s:%d matches %q", path, line, pattern.String())
}
}
}
return scanner.Err()
}