From cf91807fab36ba0f0bb73420887ed6937ecf293b Mon Sep 17 00:00:00 2001 From: Mikhail Date: Sun, 23 Aug 2026 21:21:06 +0300 Subject: [PATCH] feat(governance): add local bounded verification executor --- adapters/developmentexecutor/executor.go | 197 ++++++++++++++++++ adapters/developmentexecutor/executor_test.go | 70 +++++++ dev/ACTIVE_TASK.json | 4 +- ...-local-governed-verification-executor.json | 23 ++ 4 files changed, 292 insertions(+), 2 deletions(-) create mode 100644 adapters/developmentexecutor/executor.go create mode 100644 adapters/developmentexecutor/executor_test.go create mode 100644 dev/tasks/DEV-000252-local-governed-verification-executor.json diff --git a/adapters/developmentexecutor/executor.go b/adapters/developmentexecutor/executor.go new file mode 100644 index 0000000..de9faa4 --- /dev/null +++ b/adapters/developmentexecutor/executor.go @@ -0,0 +1,197 @@ +// Package developmentexecutor contains the local, read-only verification +// adapter for governed development candidates. It is deliberately not a +// source mutator, promotion authority, secret resolver, or deployment tool. +package developmentexecutor + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "strings" + "time" +) + +type CheckKind string + +const ( + CheckGoTest CheckKind = "GO_TEST" + CheckGoVet CheckKind = "GO_VET" + CheckGitDiffCheck CheckKind = "GIT_DIFF_CHECK" +) + +type Check struct { + Kind CheckKind + Packages []string +} + +type Plan struct { + Worktree string + WorktreeID string + BaseRepoRevision string + ProtectedMainAccess string + ProductionAccess string + SecretAccess string + Checks []Check +} + +type CheckResult struct { + Kind CheckKind `json:"kind"` + Passed bool `json:"passed"` + ExitCode int `json:"exit_code"` + DurationMillis int64 `json:"duration_millis"` +} + +type Result struct { + WorktreeID string `json:"worktree_id"` + BaseRepoRevision string `json:"base_repo_revision"` + Passed bool `json:"passed"` + Checks []CheckResult `json:"checks"` + EvidenceRef string `json:"evidence_ref"` +} + +var ( + ErrInvalidPlan = errors.New("invalid governed verification plan") + ErrUnsupportedCheck = errors.New("unsupported governed verification check") + ErrProtectedWorktree = errors.New("protected worktree is not executable") +) + +type CommandRunner interface { + Run(context.Context, string, string, []string) int +} + +type OSCommandRunner struct{} + +func (OSCommandRunner) Run(ctx context.Context, dir, name string, args []string) int { + command := exec.CommandContext(ctx, name, args...) + command.Dir = dir + command.Stdin = nil + command.Stdout = io.Discard + command.Stderr = io.Discard + command.Env = safeEnvironment() + if err := command.Run(); err != nil { + if exitErr, ok := err.(*exec.ExitError); ok { + return exitErr.ExitCode() + } + return -1 + } + return 0 +} + +func ValidatePlan(plan Plan) error { + if strings.TrimSpace(plan.Worktree) == "" || strings.TrimSpace(plan.WorktreeID) == "" || strings.TrimSpace(plan.BaseRepoRevision) == "" || len(plan.Checks) == 0 { + return ErrInvalidPlan + } + if !filepath.IsAbs(plan.Worktree) { + return ErrInvalidPlan + } + if plan.ProtectedMainAccess != "DENIED" || plan.ProductionAccess != "DENIED" || plan.SecretAccess != "DENIED" { + return ErrProtectedWorktree + } + switch strings.ToLower(filepath.Base(filepath.Clean(plan.WorktreeID))) { + case "main", "protected-main", "production": + return ErrProtectedWorktree + } + for _, check := range plan.Checks { + if err := validateCheck(check); err != nil { + return err + } + } + return nil +} + +func validateCheck(check Check) error { + switch check.Kind { + case CheckGoTest, CheckGoVet: + if len(check.Packages) == 0 { + return ErrInvalidPlan + } + for _, packagePath := range check.Packages { + if strings.TrimSpace(packagePath) == "" || strings.ContainsAny(packagePath, ";&|`$()<>\r\n") || unsafePackagePath(packagePath) { + return ErrInvalidPlan + } + } + case CheckGitDiffCheck: + if len(check.Packages) != 0 { + return ErrInvalidPlan + } + default: + return ErrUnsupportedCheck + } + return nil +} + +func unsafePackagePath(value string) bool { + value = strings.ReplaceAll(value, "\\", "/") + for _, part := range strings.Split(value, "/") { + if part == ".." { + return true + } + } + return strings.HasPrefix(value, "../") || strings.HasPrefix(value, "/") || (len(value) > 1 && value[1] == ':') +} + +func Execute(ctx context.Context, plan Plan, runner CommandRunner) (Result, error) { + if err := ValidatePlan(plan); err != nil { + return Result{}, err + } + if runner == nil { + runner = OSCommandRunner{} + } + if _, err := os.Stat(plan.Worktree); err != nil { + return Result{}, fmt.Errorf("worktree unavailable: %w", err) + } + result := Result{WorktreeID: plan.WorktreeID, BaseRepoRevision: plan.BaseRepoRevision, Passed: true, Checks: make([]CheckResult, 0, len(plan.Checks))} + for _, check := range plan.Checks { + started := time.Now() + name, args := commandFor(check) + exitCode := runner.Run(ctx, plan.Worktree, name, args) + passed := exitCode == 0 + result.Checks = append(result.Checks, CheckResult{Kind: check.Kind, Passed: passed, ExitCode: exitCode, DurationMillis: time.Since(started).Milliseconds()}) + if !passed { + result.Passed = false + } + if ctx.Err() != nil { + result.Passed = false + break + } + } + result.EvidenceRef = evidenceRef(result) + return result, nil +} + +func commandFor(check Check) (string, []string) { + switch check.Kind { + case CheckGoTest: + return "go", append([]string{"test"}, check.Packages...) + case CheckGoVet: + return "go", append([]string{"vet"}, check.Packages...) + default: + return "git", []string{"diff", "--check"} + } +} + +func evidenceRef(result Result) string { + hash := sha256.New() + _, _ = fmt.Fprintf(hash, "%s|%s|%t|", result.WorktreeID, result.BaseRepoRevision, result.Passed) + for _, check := range result.Checks { + _, _ = fmt.Fprintf(hash, "%s|%t|%d|", check.Kind, check.Passed, check.ExitCode) + } + return "verification:" + hex.EncodeToString(hash.Sum(nil)) +} + +func safeEnvironment() []string { + allowed := []string{"PATH", "SystemRoot", "TEMP", "TMP", "GOROOT", "GOPATH", "GOMODCACHE"} + result := make([]string, 0, len(allowed)) + for _, name := range allowed { + if value, ok := os.LookupEnv(name); ok && value != "" { + result = append(result, name+"="+value) + } + } + return result +} diff --git a/adapters/developmentexecutor/executor_test.go b/adapters/developmentexecutor/executor_test.go new file mode 100644 index 0000000..09b018c --- /dev/null +++ b/adapters/developmentexecutor/executor_test.go @@ -0,0 +1,70 @@ +package developmentexecutor + +import ( + "context" + "errors" + "path/filepath" + "testing" +) + +type fakeRunner struct { + calls []string + exits []int +} + +func (r *fakeRunner) Run(_ context.Context, _ string, name string, args []string) int { + call := name + for _, arg := range args { + call += " " + arg + } + r.calls = append(r.calls, call) + if len(r.exits) == 0 { + return 0 + } + exit := r.exits[0] + r.exits = r.exits[1:] + return exit +} + +func validPlan(t *testing.T) Plan { + t.Helper() + worktree, err := filepath.Abs(".") + if err != nil { + t.Fatal(err) + } + return Plan{Worktree: worktree, WorktreeID: "sandbox-1", BaseRepoRevision: "revision-1", ProtectedMainAccess: "DENIED", ProductionAccess: "DENIED", SecretAccess: "DENIED", Checks: []Check{{Kind: CheckGoTest, Packages: []string{"./modules/work/..."}}, {Kind: CheckGoVet, Packages: []string{"./modules/work/..."}}, {Kind: CheckGitDiffCheck}}} +} + +func TestExecuteRunsOnlyTypedChecksAndReturnsEvidence(t *testing.T) { + runner := &fakeRunner{} + result, err := Execute(context.Background(), validPlan(t), runner) + if err != nil || !result.Passed || len(result.Checks) != 3 || result.EvidenceRef == "" { + t.Fatalf("result=%+v err=%v", result, err) + } + if len(runner.calls) != 3 || runner.calls[0] != "go test ./modules/work/..." || runner.calls[2] != "git diff --check" { + t.Fatalf("calls=%v", runner.calls) + } +} + +func TestExecuteFailureCannotBecomeVerificationSuccess(t *testing.T) { + runner := &fakeRunner{exits: []int{1, 0}} + plan := validPlan(t) + plan.Checks = []Check{{Kind: CheckGoTest, Packages: []string{"./..."}}, {Kind: CheckGitDiffCheck}} + result, err := Execute(context.Background(), plan, runner) + if err != nil || result.Passed || result.Checks[0].Passed || result.EvidenceRef == "" { + t.Fatalf("failed result was overclaimed: %+v err=%v", result, err) + } +} + +func TestValidatePlanRejectsProtectedAndShellLikeInputs(t *testing.T) { + protected := validPlan(t) + protected.WorktreeID = "production" + if !errors.Is(ValidatePlan(protected), ErrProtectedWorktree) { + t.Fatal("protected worktree accepted") + } + injection := validPlan(t) + injection.Checks = []Check{{Kind: CheckGoTest, Packages: []string{"./...; echo leaked"}}} + if !errors.Is(ValidatePlan(injection), ErrInvalidPlan) { + t.Fatal("shell-like package accepted") + } +} diff --git a/dev/ACTIVE_TASK.json b/dev/ACTIVE_TASK.json index 2f31d37..dc3c3a0 100644 --- a/dev/ACTIVE_TASK.json +++ b/dev/ACTIVE_TASK.json @@ -1,5 +1,5 @@ { - "task_file": "dev/tasks/DEV-000251-reflection-cognition-routing.json", - "task_id": "DEV-000251", + "task_file": "dev/tasks/DEV-000252-local-governed-verification-executor.json", + "task_id": "DEV-000252", "task_revision": 1 } diff --git a/dev/tasks/DEV-000252-local-governed-verification-executor.json b/dev/tasks/DEV-000252-local-governed-verification-executor.json new file mode 100644 index 0000000..5103ec8 --- /dev/null +++ b/dev/tasks/DEV-000252-local-governed-verification-executor.json @@ -0,0 +1,23 @@ +{ + "task_id": "DEV-000252", + "task_revision": 1, + "architecture_revision": "ADR-0018", + "base_repo_revision": "c925089fd2304213758d2721a0592ca73d765047", + "risk_tier": "E", + "intent": "Add a local governed verification executor that runs only typed, bounded checks inside an isolated worktree and returns evidence without source, secret, production or promotion authority.", + "requirement_ids": ["governed self-development", "isolated verification", "independent test evidence"], + "allowed_change_scope": {"modules": ["governed-development"], "paths": ["adapters/developmentexecutor/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000252-local-governed-verification-executor.json"]}, + "forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "contracts/**", "LLM-local/**", "modules/work/**", "cmd/core-runtime/**", "cmd/agent-worker/**"]}, + "allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0}, + "contracts": {"consume": ["existing DevelopmentScaffold semantics", "typed local check plan"], "may_change": []}, + "semantic_owner": "governed development verification adapter", + "canonical_state": ["none; executor returns evidence to a future gate owner"], + "state_transitions": ["typed plan -> validated sandbox checks -> immutable result summary"], + "side_effects_and_privileges": ["read-only source/test execution in supplied isolated worktree; no secrets, production, protected main or promotion access"], + "persistence_and_migrations": ["none"], + "failure_retry_restart": ["each check has an explicit result; failed or cancelled checks never become verification success; rerun creates a new result"], + "assumptions": ["worktree isolation and gate persistence are supplied by the surrounding governed control plane"], + "acceptance_scenarios": ["allowed go test/vet and git diff check plans validate", "shell injection and unsupported commands reject", "protected or production worktree rejects", "failed check returns non-success evidence", "result contains no command output or secret material"], + "required_tests": ["go test ./adapters/developmentexecutor", "go vet ./adapters/developmentexecutor", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "git diff --check", "independent", "CI"], + "terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"] +} -- 2.54.0