projectautomation

package
v0.3.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 23, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Index

Constants

View Source
const (
	GovernanceActionKindAutomationRunStart = "automation_run_start"
	GovernanceOutcomeKindAutomationAttempt = "automation_attempt"
)
View Source
const (
	AutomationStatusDraft      = "draft"
	AutomationStatusEnabled    = "enabled"
	AutomationStatusDisabled   = "disabled"
	AutomationStatusRunning    = "running"
	AutomationStatusPaused     = "paused"
	AutomationStatusFailed     = "failed"
	AutomationStatusCancelled  = "cancelled"
	AutomationStatusSuperseded = "superseded"
)
View Source
const (
	RunStatusQueued            = "queued"
	RunStatusClaiming          = "claiming"
	RunStatusStarting          = "starting"
	RunStatusRunning           = "running"
	RunStatusVerifying         = "verifying"
	RunStatusCompleted         = "completed"
	RunStatusFailed            = "failed"
	RunStatusBlocked           = "blocked"
	RunStatusCancelled         = "cancelled"
	RunStatusPolicyDenied      = "policy_denied"
	RunStatusRunnerUnavailable = "runner_unavailable"
	RunStatusTimeout           = "timeout"
)
View Source
const (
	BatchStatusPlanned   = "planned"
	BatchStatusRunning   = "running"
	BatchStatusCompleted = "completed"
	BatchStatusFailed    = "failed"
	BatchStatusBlocked   = "blocked"
	BatchStatusCancelled = "cancelled"
)
View Source
const (
	RunnerKindCodexCLI = "codex_cli"
	RunnerKindCrushCLI = "crush_cli"
	RunnerKindManual   = "manual"
)
View Source
const (
	RunnerExecutionInProcess = "in_process"
	RunnerExecutionExternal  = "external"
	RunnerExecutionManaged   = "managed"
)
View Source
const (
	TriggerKindManual    = "manual"
	TriggerKindAutomatic = "automatic"
)
View Source
const (
	AutomationSourceManual   = "manual"
	AutomationSourceWorkflow = "workflow"
)
View Source
const DefaultClaimIdleSleepInterval = 200 * time.Millisecond

DefaultClaimIdleSleepInterval bounds the claim-loop spin rate when a full recovery cycle claims nothing (Bug #4). Caps the worst case at ~5 Hz so that even an un-memoized recovery path cannot re-pin the CPU at ~184 Hz. The memoization fix (RunSafeSummaryGitOpsPostTaskNonRecoverable) is the primary remedy; this sleep is defense-in-depth. Exported so the server binary wires the canonical interval via SetClaimIdleSleep.

View Source
const PermissionSnapshotRefPrefix = "permission_snapshot:"
View Source
const RunSafeSummaryGitOpsPostTaskNonRecoverable = "gitops_post_task_non_recoverable"

RunSafeSummaryGitOpsPostTaskNonRecoverable marks a failed run that the GitOps post-task recovery scan (claimGitOpsPostTaskRecovery) has determined is NOT recoverable (RunnerKind != codex_cli, or a failure category outside isRecoverableGitOpsPostTaskFailure). Bug #4: without this marker the scan re-evaluated and re-logged such runs on EVERY claim cycle (~184 Hz), pinning the CPU and starving the reconciler. The first scan stamps this marker so subsequent cycles skip the run with a single SafeSummary check — no re-list, re-filter, re-evaluate, or re-log. Cleared by operator recovery (reset_recovery / retry paths that rewrite SafeSummary), so a reopened run gets a fresh evaluation. The value is stable and must not change.

View Source
const RunSafeSummaryGitOpsPostTaskRecovery = "gitops_post_task_recovery"
View Source
const RunSafeSummaryGitOpsRecoveryRequeuedImplementation = "gitops_recovery_requeued_implementation"
View Source
const RunSafeSummaryPostImplementationReviewQueued = "post_implementation_review_queued"
View Source
const RunSafeSummaryVerifiedTaskDone = "external_codex_cli_verified_task_done"
View Source
const RunSafeSummaryWorkflowChainGitOpsRepairRetryFailed = "workflow_chain_gitops_repair_retry_failed"

RunSafeSummaryWorkflowChainGitOpsRepairRetryFailed marks a run that reached the post-repair GitOps retry step and could not proceed (e.g. the chain was not ready for retry, or verification failed again). It is a terminal closeout: the parent repair task is already Done by the time this is written, and the run is not recoverable through the standard GitOps post-task recovery scan. The recovery claim and reconcile paths recognize this marker and skip the run silently instead of re-evaluating it every cycle. The value is stable and must not change: existing runs in stores already carry the bare literal "workflow_chain_gitops_repair_retry_failed".

Variables

View Source
var ErrClaimRace = errors.New("automation run claim lost race")

ErrClaimRace signals that a ClaimRun conditional write lost the race: the run was no longer in RunStatusQueued when the write was attempted (another claimer transitioned it first). Callers treat it as a benign lost race and advance to the next candidate, mirroring claimGitOpsPostTaskRecovery's sameRecoveryClaim handling. It is the store-level primitive that makes the Queued->Claiming claim transition correct without holding the per-project claim mutex across the write.

View Source
var ErrConditionMismatch = errors.New("automation run condition mismatch")

ErrConditionMismatch signals that a UpdateRunIf conditional write did not apply: the persisted run's Status or ClaimID no longer matched the expected condition (e.g. a concurrent claim bumped ClaimID). Callers treat it as a benign lost race and skip the write, mirroring requeueAbandonedRunningRun's GetRun+identity-check guard. It is the store-level primitive the reconcile terminal-write guards use to avoid clobbering a concurrently-claimed run.

View Source
var ErrInvalidInput = errors.New("invalid project automation input")

Functions

func DetectCodex

func DetectCodex(binaryPath string) (string, bool)

func GovernedWorkflowStepInstructions added in v0.3.0

func GovernedWorkflowStepInstructions(taskRef string) []string

func ImplementationTaskCloseoutInstructions added in v0.3.0

func ImplementationTaskCloseoutInstructions(taskRef string, filesToEdit []string) []string

func IsExternalRunnerKind added in v0.3.0

func IsExternalRunnerKind(kind string) bool

IsExternalRunnerKind reports whether kind is an externally-executed runner kind (one backed by an out-of-process harness like codex or crush, claimed and reported through ClaimNextRun/CompleteAttempt). RunnerKindManual and any unknown kind return false. This is the single allowlist used by the claim guard, CompleteAttempt gate, and queue filter so all three stay in sync as new external harnesses are added.

func IsTerminalRunStatus added in v0.3.0

func IsTerminalRunStatus(status string) bool

IsTerminalRunStatus reports whether the given automation run status is terminal (no further state transitions expected). Exported so the store layer can use it for purge safety guards.

func RenderCodexTaskPrompt added in v0.2.1

func RenderCodexTaskPrompt(input CodexTaskInput) string

func RunnerFailureCategoryIsHardBlocker added in v0.3.1

func RunnerFailureCategoryIsHardBlocker(category string) bool

RunnerFailureCategoryIsHardBlocker reports whether a failure category denotes a permanent (non-code-repairable) failure — a config, permission, or auth problem a validation-reviewer repair task cannot fix. It is the single source of truth shared by the projectautomation recovery classifier and the chain-layer repairability classifier, so the two layers cannot drift and re-introduce a cross-layer repair loop (stuck-runs audit 2026-06-22 Problem 3, review x2 F4/F7).

Types

type Automation

type Automation struct {
	ID                    string   `json:"id"`
	ProjectID             string   `json:"project_id"`
	AutomationRef         string   `json:"automation_ref"`
	Title                 string   `json:"title"`
	Purpose               string   `json:"purpose"`
	Status                string   `json:"status"`
	AgentID               string   `json:"agent_id"`
	PlanID                string   `json:"plan_id,omitempty"`
	AllowedTaskRefs       []string `json:"allowed_task_refs,omitempty"`
	RequiredReviewTaskIDs []string `json:"required_review_task_ids,omitempty"`
	TriggerKind           string   `json:"trigger_kind"`
	SourceKind            string   `json:"source_kind,omitempty"`
	SchedulePolicy        string   `json:"schedule_policy,omitempty"`
	PermissionRef         string   `json:"permission_ref"`
	CreatedByRunID        string   `json:"created_by_run_id,omitempty"`
	TraceID               string   `json:"trace_id,omitempty"`
	// Harness/Model/Provider carry the per-agent harness selection from the
	// workflow definition through to the runner. Empty means inherit the
	// server/runner default (codex). Populated by the workflow compiler from
	// the [[agents]] block; read by claimedRunForAutomation to surface on the
	// ClaimedRun response so the runner can dispatch per-run.
	Harness   string    `json:"harness,omitempty"`
	Model     string    `json:"model,omitempty"`
	Provider  string    `json:"provider,omitempty"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

type AutomationAgent

type AutomationAgent struct {
	ID              string        `json:"id"`
	DisplayName     string        `json:"display_name"`
	Purpose         string        `json:"purpose"`
	Enabled         bool          `json:"enabled"`
	AllowedSkills   []string      `json:"allowed_skills,omitempty"`
	AllowedTools    []string      `json:"allowed_tools,omitempty"`
	AllowedCommands []CommandSpec `json:"allowed_commands,omitempty"`
	DeniedCommands  []string      `json:"denied_commands,omitempty"`
	WorkspaceMode   string        `json:"workspace_mode"`
	NetworkPolicy   string        `json:"network_policy"`
	SecretPolicy    string        `json:"secret_policy"`
	LogPolicy       string        `json:"log_policy"`
	MaxRuntime      time.Duration `json:"max_runtime"`
	MaxRetries      int           `json:"max_retries"`
}

type AutomationAttempt

type AutomationAttempt struct {
	ID                 string    `json:"id"`
	ProjectID          string    `json:"project_id"`
	AutomationRunID    string    `json:"automation_run_id"`
	AttemptNumber      int       `json:"attempt_number"`
	RunnerKind         string    `json:"runner_kind"`
	CommandRef         string    `json:"command_ref,omitempty"`
	InputSummaryHash   string    `json:"input_summary_hash,omitempty"`
	OutputSummaryHash  string    `json:"output_summary_hash,omitempty"`
	Status             string    `json:"status"`
	FailureCategory    string    `json:"failure_category,omitempty"`
	DurationMS         int64     `json:"duration_ms"`
	VerifierResultRefs []string  `json:"verifier_result_refs,omitempty"`
	EvidenceRefs       []string  `json:"evidence_refs,omitempty"`
	ClaimRefs          []string  `json:"claim_refs,omitempty"`
	KnowledgeRefs      []string  `json:"knowledge_candidate_refs,omitempty"`
	CreatedAt          time.Time `json:"created_at"`
	FinishedAt         time.Time `json:"finished_at,omitempty"`
}

type AutomationDeleteCounts added in v0.3.0

type AutomationDeleteCounts struct {
	Automations int `json:"automations"`
	Runs        int `json:"runs"`
}

AutomationDeleteCounts tallies what an automation plan-cascade delete removed.

type AutomationFilter

type AutomationFilter struct {
	ProjectID string `json:"project_id,omitempty"`
	Status    string `json:"status,omitempty"`
	AgentID   string `json:"agent_id,omitempty"`
	PlanID    string `json:"plan_id,omitempty"`
}

type AutomationParallelBatch

type AutomationParallelBatch struct {
	ID                string    `json:"id"`
	ProjectID         string    `json:"project_id"`
	AutomationRunID   string    `json:"automation_run_id"`
	OrchestratorRunID string    `json:"orchestrator_run_id"`
	PlanID            string    `json:"plan_id"`
	TaskIDs           []string  `json:"task_ids"`
	Status            string    `json:"status"`
	SafetyReason      string    `json:"safety_reason"`
	ConflictSummary   string    `json:"conflict_summary,omitempty"`
	CreatedAt         time.Time `json:"created_at"`
	UpdatedAt         time.Time `json:"updated_at"`
}

type AutomationRun

type AutomationRun struct {
	ID                string   `json:"id"`
	ProjectID         string   `json:"project_id"`
	AutomationID      string   `json:"automation_id"`
	AgentID           string   `json:"agent_id"`
	PlanID            string   `json:"plan_id"`
	TaskID            string   `json:"task_id"`
	WorkTaskStatus    string   `json:"work_task_status"`
	Status            string   `json:"status"`
	RunnerKind        string   `json:"runner_kind"`
	AgentRunID        string   `json:"agent_run_id,omitempty"`
	TraceID           string   `json:"trace_id,omitempty"`
	AttemptCount      int      `json:"attempt_count"`
	OrchestratorRunID string   `json:"orchestrator_run_id,omitempty"`
	ParentRunID       string   `json:"parent_run_id,omitempty"`
	WorkerRunIDs      []string `json:"worker_run_ids,omitempty"`
	ParallelGroupID   string   `json:"parallel_group_id,omitempty"`
	FailureCategory   string   `json:"failure_category,omitempty"`
	SafeSummary       string   `json:"safe_summary,omitempty"`
	// OperatorDiagnostics carries unsanitized failure detail (absolute paths, git
	// commands, refs) populated by the runner on failure. It is OPERATOR-ONLY: the
	// MCP boundary (mcp_adapter.go) projects it out of agent responses unless the
	// caller passes include_diagnostics=true. Agents never see it by default, so
	// the privacy boundary (no roots/paths/commands to agents) is preserved.
	OperatorDiagnostics string    `json:"operator_diagnostics,omitempty"`
	ClaimID             string    `json:"claim_id,omitempty"`
	RunnerID            string    `json:"runner_id,omitempty"`
	ClaimedAt           time.Time `json:"claimed_at,omitempty"`
	LastHeartbeatAt     time.Time `json:"last_heartbeat_at,omitempty"`
	LeaseExpiresAt      time.Time `json:"lease_expires_at,omitempty"`
	StartedAt           time.Time `json:"started_at,omitempty"`
	FinishedAt          time.Time `json:"finished_at,omitempty"`
	CreatedAt           time.Time `json:"created_at"`
	UpdatedAt           time.Time `json:"updated_at"`
}

type CancelRunInput added in v0.3.0

type CancelRunInput struct {
	ProjectID       string   `json:"project_id,omitempty"`
	RunID           string   `json:"run_id"`
	FailureCategory string   `json:"failure_category,omitempty"`
	SafeSummary     string   `json:"safe_summary,omitempty"`
	EvidenceRefs    []string `json:"evidence_refs,omitempty"`
}

type ClaimNextRunInput

type ClaimNextRunInput struct {
	ProjectID  string `json:"project_id,omitempty"`
	AgentID    string `json:"agent_id,omitempty"`
	RunnerKind string `json:"runner_kind,omitempty"`
	RunnerID   string `json:"runner_id,omitempty"`
}

type ClaimedRun

type ClaimedRun struct {
	Run        AutomationRun  `json:"run"`
	Automation *Automation    `json:"automation,omitempty"`
	CodexInput CodexTaskInput `json:"codex_input"`
	TimeoutMS  int64          `json:"timeout_ms"`
	// AgentHarness/AgentModel/AgentProvider carry the per-agent harness
	// selection from the workflow definition, resolved at claim time from the
	// automation row. Empty means the runner uses its startup default. The
	// runner reads these to dispatch the claimed run to the correct adapter
	// (codex or crush) with the right model/provider — Phase 3 per-agent
	// routing.
	AgentHarness  string `json:"agent_harness,omitempty"`
	AgentModel    string `json:"agent_model,omitempty"`
	AgentProvider string `json:"agent_provider,omitempty"`
}

type CodexCommand

type CodexCommand struct {
	Path      string
	Args      []string
	Env       []string
	StdinFile string
	Dir       string
	Timeout   time.Duration
}

func BuildCodexCommand

func BuildCodexCommand(input CodexCommandInput) (CodexCommand, error)

type CodexCommandInput

type CodexCommandInput struct {
	BinaryPath string
	InputPath  string
	Timeout    time.Duration
	EnvAllow   map[string]string
}

type CodexRunResult

type CodexRunResult struct {
	ExitCode            int
	Duration            time.Duration
	TimedOut            bool
	OutputTruncated     bool
	SafeFailureCategory string
	Output              string
}

func RunCodexCommand

func RunCodexCommand(ctx context.Context, command CodexCommand, maxOutputBytes int64) (CodexRunResult, error)

type CodexTaskInput

type CodexTaskInput struct {
	SchemaVersion           int      `json:"schema_version"`
	ProjectID               string   `json:"project_id"`
	MCPServerURL            string   `json:"mcp_server_url,omitempty"`
	AutomationRunID         string   `json:"automation_run_id"`
	TraceID                 string   `json:"trace_id,omitempty"`
	PlanID                  string   `json:"plan_id"`
	TaskID                  string   `json:"task_id"`
	TaskRef                 string   `json:"task_ref"`
	Title                   string   `json:"title"`
	Description             string   `json:"description,omitempty"`
	EvidenceNeeded          []string `json:"evidence_needed,omitempty"`
	ContextPackRefs         []string `json:"context_pack_refs,omitempty"`
	WorkPlanContext         []string `json:"work_plan_context,omitempty"`
	DependencyContext       []string `json:"dependency_context,omitempty"`
	FilesToRead             []string `json:"files_to_read,omitempty"`
	LikelyFilesAffected     []string `json:"likely_files_affected,omitempty"`
	VerificationRequirement string   `json:"verification_requirement"`
	ExpectedOutput          string   `json:"expected_output,omitempty"`
	FailureCriteria         string   `json:"failure_criteria,omitempty"`
	ResumeInstructions      string   `json:"resume_instructions,omitempty"`
	AcceptanceCriteria      []string `json:"acceptance_criteria,omitempty"`
	StopConditions          []string `json:"stop_conditions,omitempty"`
	VerifierLadder          []string `json:"verifier_ladder,omitempty"`
	RegressionApplicability string   `json:"regression_test_applicability,omitempty"`
	DownstreamImpactRefs    []string `json:"downstream_impact_refs,omitempty"`
	OutputContract          string   `json:"output_contract,omitempty"`
	RunnerInstructions      []string `json:"runner_instructions"`
}

type CommandSpec

type CommandSpec struct {
	Command string   `json:"command"`
	Args    []string `json:"args,omitempty"`
}

type CompleteAttemptInput

type CompleteAttemptInput struct {
	ProjectID          string   `json:"project_id,omitempty"`
	RunID              string   `json:"run_id"`
	ClaimID            string   `json:"claim_id,omitempty"`
	RunnerID           string   `json:"runner_id,omitempty"`
	Status             string   `json:"status"`
	FailureCategory    string   `json:"failure_category,omitempty"`
	DurationMS         int64    `json:"duration_ms,omitempty"`
	VerifierResultRefs []string `json:"verifier_result_refs,omitempty"`
	EvidenceRefs       []string `json:"evidence_refs,omitempty"`
	ClaimRefs          []string `json:"claim_refs,omitempty"`
	ReviewRefs         []string `json:"review_result_refs,omitempty"`
	KnowledgeRefs      []string `json:"knowledge_candidate_refs,omitempty"`
}

type ComputeParallelBatchInput

type ComputeParallelBatchInput struct {
	ProjectID         string   `json:"project_id,omitempty"`
	AutomationRunID   string   `json:"automation_run_id,omitempty"`
	OrchestratorRunID string   `json:"orchestrator_run_id,omitempty"`
	PlanID            string   `json:"plan_id,omitempty"`
	TaskIDs           []string `json:"task_ids,omitempty"`
	MaxTasks          int      `json:"max_tasks,omitempty"`
}

type ConfidenceGovernance added in v0.2.0

type ConfidenceGovernance interface {
	RecordConfidenceRef(context.Context, GovernanceConfidenceInput) (string, error)
}

type CreateAutomationInput

type CreateAutomationInput struct {
	ProjectID             string   `json:"project_id,omitempty"`
	AutomationRef         string   `json:"automation_ref"`
	Title                 string   `json:"title"`
	Purpose               string   `json:"purpose"`
	Status                string   `json:"status,omitempty"`
	AgentID               string   `json:"agent_id"`
	PlanID                string   `json:"plan_id,omitempty"`
	AllowedTaskRefs       []string `json:"allowed_task_refs,omitempty"`
	RequiredReviewTaskIDs []string `json:"required_review_task_ids,omitempty"`
	TriggerKind           string   `json:"trigger_kind,omitempty"`
	SchedulePolicy        string   `json:"schedule_policy,omitempty"`
	PermissionRef         string   `json:"permission_ref"`
	SourceKind            string   `json:"source_kind,omitempty"`
	CreatedByRunID        string   `json:"created_by_run_id,omitempty"`
	TraceID               string   `json:"trace_id,omitempty"`
	Harness               string   `json:"harness,omitempty"`
	Model                 string   `json:"model,omitempty"`
	Provider              string   `json:"provider,omitempty"`
}

type CreateRemediationFromFindingInput added in v0.2.4

type CreateRemediationFromFindingInput struct {
	ProjectID               string   `json:"project_id,omitempty"`
	FindingRef              string   `json:"finding_ref"`
	FindingStatus           string   `json:"finding_status"`
	Title                   string   `json:"title"`
	Summary                 string   `json:"summary"`
	Severity                string   `json:"severity,omitempty"`
	OwnerAgent              string   `json:"owner_agent,omitempty"`
	ImplementationAgentID   string   `json:"implementation_agent_id,omitempty"`
	CreatedByRunID          string   `json:"created_by_run_id,omitempty"`
	TraceID                 string   `json:"trace_id,omitempty"`
	PermissionSnapshotRef   string   `json:"permission_snapshot_ref,omitempty"`
	GitBaseRef              string   `json:"git_base_ref,omitempty"`
	GitBranchRef            string   `json:"git_branch_ref,omitempty"`
	GitWorktreeRef          string   `json:"git_worktree_ref,omitempty"`
	FilesToRead             []string `json:"files_to_read,omitempty"`
	FilesToEdit             []string `json:"files_to_edit,omitempty"`
	LikelyFilesAffected     []string `json:"likely_files_affected,omitempty"`
	EvidenceRefs            []string `json:"evidence_refs,omitempty"`
	VerificationRequirement string   `json:"verification_requirement"`
	ReviewGate              string   `json:"review_gate,omitempty"`
	ActivatePlan            bool     `json:"activate_plan,omitempty"`
}

type CreateRemediationFromFindingResult added in v0.2.4

type CreateRemediationFromFindingResult struct {
	WorkPlan         projectworkplan.WorkPlan `json:"work_plan"`
	WorkTask         projectworkplan.WorkTask `json:"work_task"`
	ReviewTask       projectworkplan.WorkTask `json:"review_task,omitempty"`
	Automation       Automation               `json:"automation"`
	ReviewAutomation Automation               `json:"review_automation,omitempty"`
	Activated        bool                     `json:"activated"`
}

type DirtyScopeRecoveryOptions added in v0.2.4

type DirtyScopeRecoveryOptions struct {
	AllowedSupportPathspecs []string
	PathspecResolver        func(projectID string) []string
}

type EvidenceGovernance added in v0.2.0

type EvidenceGovernance interface {
	CreateActionRef(context.Context, GovernanceActionInput) (string, error)
	CreateOutcomeRef(context.Context, GovernanceOutcomeInput) (string, error)
}

type Executor added in v0.2.0

type Executor struct {
	// contains filtered or unexported fields
}

func NewExecutor added in v0.2.0

func NewExecutor(service *Service, options ExecutorOptions) *Executor

func (*Executor) Start added in v0.2.0

func (executor *Executor) Start(ctx context.Context) error

func (*Executor) Stop added in v0.2.0

func (executor *Executor) Stop(ctx context.Context) error

type ExecutorOptions added in v0.2.0

type ExecutorOptions struct {
	Enabled               bool
	RunnerEnabled         bool
	RunnerExecution       string
	PollInterval          time.Duration
	GlobalWorkerCount     int
	PerProjectWorkerLimit int
	PerAgentWorkerLimit   int
	ProjectIDs            []string
}

type GovernanceActionInput added in v0.2.0

type GovernanceActionInput struct {
	ProjectID    string
	AutomationID string
	RunID        string
	TaskID       string
	ActionKind   string
}

type GovernanceConfidenceInput added in v0.2.0

type GovernanceConfidenceInput struct {
	ProjectID    string
	RunID        string
	TaskID       string
	ClaimRefs    []string
	EvidenceRefs []string
	VerifierRefs []string
	ReviewRefs   []string
	OutcomeRef   string
}

type GovernanceKnowledgeCandidateInput added in v0.2.0

type GovernanceKnowledgeCandidateInput struct {
	ProjectID     string
	RunID         string
	TaskID        string
	ClaimRefs     []string
	EvidenceRefs  []string
	VerifierRefs  []string
	ReviewRefs    []string
	OutcomeRef    string
	ConfidenceRef string
}

type GovernanceOptions added in v0.2.0

type GovernanceOptions struct {
	Evidence   EvidenceGovernance
	Confidence ConfidenceGovernance
	Knowledge  KnowledgeGovernance
}

type GovernanceOutcomeInput added in v0.2.0

type GovernanceOutcomeInput struct {
	ProjectID     string
	AutomationID  string
	RunID         string
	TaskID        string
	AttemptID     string
	Status        string
	DurationMS    int64
	VerifierRefs  []string
	EvidenceRefs  []string
	ClaimRefs     []string
	ReviewRefs    []string
	KnowledgeRefs []string
}

type HeartbeatRunInput added in v0.2.4

type HeartbeatRunInput struct {
	ProjectID string `json:"project_id,omitempty"`
	RunID     string `json:"run_id"`
	ClaimID   string `json:"claim_id"`
	RunnerID  string `json:"runner_id,omitempty"`
}

type KnowledgeGovernance added in v0.2.0

type KnowledgeGovernance interface {
	CreateCandidateRef(context.Context, GovernanceKnowledgeCandidateInput) (string, error)
}

type Options

type Options struct {
	Enabled                           bool
	RunnerEnabled                     bool
	RequireCodexWhenAvailable         bool
	AllowManualRunner                 bool
	RunnerExecution                   string
	MaxParallelTasks                  int
	DefaultMaxRuntime                 time.Duration
	ExternalRunLeaseTTL               time.Duration
	DefaultMaxRetries                 int
	RecoveryMaxReplacementRunsPerTask int
	// ReworkBudgetPerTask is the maximum number of rework cycles a task may
	// undergo before it transitions to failed with outcome
	// rework_budget_exhausted. Defaults to 3 (defaultReworkBudgetPerTask). This
	// budget is INDEPENDENT from RecoveryMaxReplacementRunsPerTask: rework
	// exhaustion does not consume the fatal-retry budget (R5 partition).
	ReworkBudgetPerTask int
	// ClaimFullReconcileCooldown bounds how often a runner performs a full
	// reconciliation sweep during ClaimNextRun for a given project. Defaults to
	// 30s (defaultClaimFullReconcileCooldown) when zero. Per the automation
	// pipeline policy this runtime knob must be server/project TOML config.
	ClaimFullReconcileCooldown time.Duration
	// ClaimBroadReconcileTaskBatchLimit bounds project-wide ready/promotion work
	// performed by the broad ClaimNextRun reconcile pass. Defaults to
	// defaultClaimBroadReconcileTaskBatchLimit when zero. Per the automation
	// pipeline policy this runtime knob must be server/project TOML config.
	ClaimBroadReconcileTaskBatchLimit int
	// LeaseReaperInterval is the interval at which a background goroutine
	// reclaims expired-lease runs. Defaults to 60s when zero. Per the automation
	// pipeline policy this runtime knob must be server/project TOML config.
	LeaseReaperInterval time.Duration
	// ReviewedCloseoutReconcilerInterval is the interval at which a background
	// goroutine closes reviewed-open Work Tasks and releases their dependents,
	// independent of the throttled ClaimNextRun full-reconcile cascade. Defaults
	// to defaultReviewedCloseoutReconcilerInterval (20s) when zero. Per the
	// automation pipeline policy this runtime knob must be server/project TOML
	// config.
	ReviewedCloseoutReconcilerInterval time.Duration
	// AutomationReconcilerInterval is the interval at which a background goroutine
	// runs the full claim reconcile cascade per project, independent of the
	// throttled ClaimNextRun full-reconcile cascade. Without this, a starved claim
	// path (slow graph storage / heavy runner churn) can leave the reconcile steps
	// (ready-task queuing, plan completion, stale-claim release, review queuing)
	// effectively unrun, stalling pipelines. Defaults to
	// defaultAutomationReconcilerInterval (20s) when zero. Per the automation
	// pipeline policy this runtime knob must be server/project TOML config.
	AutomationReconcilerInterval time.Duration
	// ReviewGateTimeout is the maximum duration a task may stay in
	// needs_review/verifying without a review ref before the reconciler
	// auto-grants a review exemption (when the task has implementation
	// evidence). Default 0 = disabled. Per the automation pipeline policy this
	// runtime knob must be server/project TOML config.
	ReviewGateTimeout time.Duration
	// MaxFindingsPerAudit bounds how many confirmed high-confidence findings a
	// single area-bug-audit run will remediate. Findings beyond the cap are
	// recorded as superseded (never dropped). Default applied in the service
	// constructor (defaultMaxFindingsPerAudit = 10). Per-call MCP overrides are
	// clamped to maxFindingsPerAuditCeiling (50). Per the automation pipeline
	// policy this runtime knob must be server/project TOML config.
	MaxFindingsPerAudit   int
	CodexBinaryPath       string
	Agents                []AutomationAgent
	PermissionResolver    PermissionResolver
	Governance            GovernanceOptions
	WorkPlanStatusTrigger WorkPlanStatusTriggerOptions
	DirtyScopeRecovery    DirtyScopeRecoveryOptions
}

type PermissionCheckInput added in v0.2.0

type PermissionCheckInput struct {
	ProjectID       string
	AutomationID    string
	AutomationRef   string
	AgentID         string
	PermissionRef   string
	RunnerKind      string
	RunnerExecution string
}

type PermissionResolver added in v0.2.0

type PermissionResolver interface {
	CheckAutomationPermission(context.Context, PermissionCheckInput) (PermissionSnapshotMetadata, error)
}

type PermissionSnapshotMetadata added in v0.2.0

type PermissionSnapshotMetadata struct {
	PermissionRef      string
	AgentID            string
	AllowedRunnerKinds []string
	DeniedCommands     []string
}

type ResetGitOpsRecoveryInput added in v0.3.0

type ResetGitOpsRecoveryInput struct {
	ProjectID string `json:"project_id,omitempty"`
	RunID     string `json:"run_id,omitempty"`
}

type RunCondition added in v0.3.0

type RunCondition struct {
	Status  string
	ClaimID string
}

RunCondition is the precondition for UpdateRunIf: the conditional write only applies when the persisted run's Status and ClaimID match these values. Kept minimal (two fields) — add more only if a concrete ABA window requires it.

type RunFilter

type RunFilter struct {
	ProjectID         string `json:"project_id,omitempty"`
	AutomationID      string `json:"automation_id,omitempty"`
	AgentID           string `json:"agent_id,omitempty"`
	PlanID            string `json:"plan_id,omitempty"`
	Status            string `json:"status,omitempty"`
	OrchestratorRunID string `json:"orchestrator_run_id,omitempty"`
	FailureCategory   string `json:"failure_category,omitempty"`
	WorkTaskStatus    string `json:"work_task_status,omitempty"`
}

type Service

type Service struct {
	// contains filtered or unexported fields
}

func New

func New(store Store, workTasks WorkTaskAPI, options Options) *Service

func (*Service) CallAutomationTool

func (svc *Service) CallAutomationTool(ctx context.Context, name string, arguments json.RawMessage) (any, error)

func (*Service) CancelAutomationsForPlan added in v0.3.0

func (svc *Service) CancelAutomationsForPlan(ctx context.Context, projectID string, planID string) error

CancelAutomationsForPlan disables all enabled automations for a plan (GAP-12). Called by CancelChain so cancelled chains don't leave stale automations that pollute the reconciler's scan. Best-effort: individual failures are skipped.

func (*Service) CancelOrphanedAutomations added in v0.3.0

func (svc *Service) CancelOrphanedAutomations(ctx context.Context, projectID string, activePlanIDs map[string]bool)

CancelOrphanedAutomations disables enabled automations whose plans belong to terminal (cancelled/completed/superseded/failed) workflow chains (GAP-12). Called from the claim path so historical orphans from chains cancelled before the CancelChain fix are cleaned up automatically.

func (*Service) CancelRun added in v0.3.0

func (svc *Service) CancelRun(ctx context.Context, input CancelRunInput) (AutomationRun, error)

func (*Service) ClaimNextRun

func (svc *Service) ClaimNextRun(ctx context.Context, input ClaimNextRunInput) (ClaimedRun, error)

func (*Service) CompleteAttempt

func (svc *Service) CompleteAttempt(ctx context.Context, input CompleteAttemptInput) (AutomationRun, error)

func (*Service) ComputeParallelBatch

func (svc *Service) ComputeParallelBatch(ctx context.Context, input ComputeParallelBatchInput) (AutomationParallelBatch, error)

func (*Service) CreateAutomation

func (svc *Service) CreateAutomation(ctx context.Context, input CreateAutomationInput) (Automation, error)

func (*Service) CreateRemediationFromFinding added in v0.2.4

func (svc *Service) CreateRemediationFromFinding(ctx context.Context, input CreateRemediationFromFindingInput) (CreateRemediationFromFindingResult, error)

func (*Service) DeleteAutomationsForPlanCascade added in v0.3.0

func (svc *Service) DeleteAutomationsForPlanCascade(ctx context.Context, projectID, planID string) (AutomationDeleteCounts, error)

DeleteAutomationsForPlanCascade permanently removes every automation owned by the given plan together with each automation's runs. Automations are flipped to a terminal status BEFORE deletion so the reconciler stops touching them. Scoped by project. Idempotent: returns zero counts and nil when no automations exist for the plan. Used by the governed chain-run delete cascade.

func (*Service) ExecuteQueuedRun added in v0.2.0

func (svc *Service) ExecuteQueuedRun(ctx context.Context, projectID, runID string) (AutomationRun, error)

func (*Service) ForceCancelRun added in v0.3.0

func (svc *Service) ForceCancelRun(ctx context.Context, input CancelRunInput) (AutomationRun, error)

ForceCancelRun cancels an automation run including terminal (blocked/failed) runs. It is the operator recovery path: a terminal run counts toward the replacement-retry limit (countTerminalReplacementFailures) for its task, and a cancelled run with failure_category "operator_cancelled" is NOT counted (isTerminalReplacementFailure counts only failed recovery/codex/closeout failures). Force-cancelling a stuck terminal run therefore drops it from the counter, letting the orchestrator queue a fresh run after the root cause is fixed. This bypasses the normal CancelRun terminal guard deliberately — the operator attests the root cause is corrected. The audit trail records the operator-attributed cancellation via the attempt evidence ref.

func (*Service) GetAutomation

func (svc *Service) GetAutomation(ctx context.Context, projectID, automationID string) (Automation, error)

func (*Service) GetRun

func (svc *Service) GetRun(ctx context.Context, projectID, runID string) (AutomationRun, error)

func (*Service) HandleWorkPlanStatusChanged added in v0.2.1

func (svc *Service) HandleWorkPlanStatusChanged(ctx context.Context, event projectworkplan.WorkPlanStatusChange) error

func (*Service) HeartbeatRun added in v0.2.4

func (svc *Service) HeartbeatRun(ctx context.Context, input HeartbeatRunInput) (AutomationRun, error)

func (*Service) ListAutomations

func (svc *Service) ListAutomations(ctx context.Context, filter AutomationFilter) ([]Automation, error)

func (*Service) ListRuns

func (svc *Service) ListRuns(ctx context.Context, filter RunFilter) ([]AutomationRun, error)

func (*Service) ListRunsForStats added in v0.3.1

func (svc *Service) ListRunsForStats(ctx context.Context, filter RunFilter) ([]AutomationRun, error)

ListRunsForStats lists automation runs for read-only observability (the Control Tower snapshot) WITHOUT the reconcile side effects that ListRuns triggers. It mirrors ListRuns' safeRef/safeOptionalRef validation so the operator-controlled filter fields are gated identically, but it calls rawListRuns directly — zero reconcileRunningRuns / reconcileVerifyingRuns calls (B2). A stats read must never mutate run state: it would both slow the endpoint and risk a reconcile storm if a dashboard polls at high cadence.

Project scope passes a project filter; the global/all scope (Phase R1b) will call this once per configured project.

KEEP-IN-SYNC: the validation block below is a deliberate copy of ListRuns' (service.go:961). If a new RunFilter field is added and validated in ListRuns, add the same validation here — otherwise the stats read accepts a field the canonical path rejects. The only intentional difference is the absence of the two reconcile calls.

func (*Service) PurgeCancelledAutomations added in v0.3.0

func (svc *Service) PurgeCancelledAutomations(ctx context.Context, projectID, status string) (int, error)

PurgeCancelledAutomations permanently deletes all automation nodes (and their terminal runs) matching the given status for a project. This is the operator cleanup path for stale clutter that accumulates from cancelled chains and slows the reconciler's claim-next scan (KI-2 in RUN_OBSERVABILITY_GAP.md).

Only terminal statuses (cancelled/superseded/failed/disabled) may be purged. Non-terminal runs belonging to a purged automation are left intact as a safety guard. Returns the count of purged automation definitions.

func (*Service) PurgeTerminalRuns added in v0.3.0

func (svc *Service) PurgeTerminalRuns(ctx context.Context, projectID, status string) (int, error)

PurgeTerminalRuns permanently deletes all automation-run nodes matching the given terminal status for a project. This is the operator cleanup path for thousands of historical completed/failed/cancelled runs that slow the reconciler's claim-next scan. Only terminal statuses may be purged.

func (*Service) ReconcileReadyAutomationsForPlan added in v0.3.0

func (svc *Service) ReconcileReadyAutomationsForPlan(ctx context.Context, projectID string, planID string) error

func (*Service) ResetGitOpsRecovery added in v0.3.0

func (svc *Service) ResetGitOpsRecovery(ctx context.Context, input ResetGitOpsRecoveryInput) (AutomationRun, error)

func (*Service) RunNow

func (svc *Service) RunNow(ctx context.Context, input SubmitRunInput) (AutomationRun, error)

func (*Service) SetClaimIdleSleep added in v0.3.0

func (svc *Service) SetClaimIdleSleep(sleeper func())

SetClaimIdleSleep wires the idle backoff invoked at the end of a claimGitOpsPostTaskRecovery cycle that claimed nothing (Bug #4 defense-in-depth). The server wires a bounded sleeper so the claim loop cannot re-pin the CPU re-scanning the same failed runs; tests leave it nil (no-op) and install a recorder via installClaimIdleSleepRecorder when they assert on the backoff.

func (*Service) SetWorkflowChainGitOpsRetrier added in v0.3.0

func (svc *Service) SetWorkflowChainGitOpsRetrier(retrier func(context.Context, string, string) error)

func (*Service) SetWorkflowChainPRRefRecorder added in v0.3.0

func (svc *Service) SetWorkflowChainPRRefRecorder(recorder workflowChainPRRefRecorder)

SetWorkflowChainPRRefRecorder wires the per-task -> chain-run PR ref writeback callback (projectworkflowchain.Service.RecordChainRunPullRequestRef). Best-effort: the closeout path calls it but never aborts task completion on its error. See recordTaskPullRequestRefToChain (Bug-1 fix).

func (*Service) StartAutomationReconciler added in v0.3.0

func (svc *Service) StartAutomationReconciler(ctx context.Context)

StartAutomationReconciler launches a background goroutine that periodically runs the full claim reconcile cascade per project, independent of the throttled ClaimNextRun full-reconcile cascade. This is the B2.1 starvation fix: the cascade (ready-task queuing, plan completion, stale-claim release, review queuing, gitops recovery, etc.) is no longer reachable ONLY through the claim path, so a starved/throttled claim cascade (slow graph storage or heavy runner churn) cannot leave these reconcile steps unrun. Modeled on StartReviewedCloseoutReconciler: fire-and-forget, lifecycle bound to ctx cancellation.

The tick and any concurrent claim-path reconcile cannot double-run: both acquire the same reserveClaimFullReconcile per-project lease, so when a runner-driven full reconcile is in flight the tick skips that project (and vice versa). When the tick wins the lease it runs the identical cascade (runClaimFullReconcile) with empty agentID/runnerID so the reconcile side effects fire; no run is handed to a runner from this path (there is no runner to receive it — that remains ClaimNextRun's job).

func (*Service) StartLeaseReaper added in v0.3.0

func (svc *Service) StartLeaseReaper(ctx context.Context)

StartLeaseReaper launches a background goroutine that periodically reclaims expired-lease runs across all projects. Without this, orphaned runs (from crashed/restarted runner containers) stay stuck in running until a runner happens to poll ClaimNextRun for that project. The reaper ensures recovery even when no runner is actively polling.

func (*Service) StartReviewedCloseoutReconciler added in v0.3.0

func (svc *Service) StartReviewedCloseoutReconciler(ctx context.Context)

StartReviewedCloseoutReconciler launches a background goroutine that closes reviewed-open Work Tasks and releases their dependents on a fixed cadence. This is intentionally separate from the ClaimNextRun full-reconcile cascade: that cascade is throttled (reserveClaimFullReconcile, 30s cooldown) and runs synchronously inside the runner HTTP-poll path, so under slow graph storage or heavy runner churn it can be effectively starved — leaving reviewed tasks stuck in needs_review forever and dependents never released. This tick guarantees the closeout runs regardless of claim-path pressure. Modeled on StartLeaseReaper: fire-and-forget, lifecycle bound to ctx cancellation.

func (*Service) StartZombieReviewReaper added in v0.3.1

func (svc *Service) StartZombieReviewReaper(ctx context.Context)

StartZombieReviewReaper launches a background goroutine that periodically reaps zombie review runs — review automation runs stuck in a non-terminal status (queued/claiming/starting/running/verifying) whose parent implementation run is terminal or missing. These zombies suppress second-review spawns after a rework cycle because the dedup in queuePostImplementationReviewRun treats them as live duplicates (R3). Modeled on StartReviewedCloseoutReconciler: fire-and-forget, lifecycle bound to ctx cancellation, per-project isolation.

func (*Service) SubmitRun

func (svc *Service) SubmitRun(ctx context.Context, input SubmitRunInput) (AutomationRun, error)

func (*Service) UpdateAutomationStatus added in v0.2.2

func (svc *Service) UpdateAutomationStatus(ctx context.Context, input UpdateAutomationStatusInput) (Automation, error)

type Store

type Store interface {
	CreateAutomation(context.Context, Automation) (Automation, error)
	GetAutomation(context.Context, string, string) (Automation, error)
	ListAutomations(context.Context, AutomationFilter) ([]Automation, error)
	UpdateAutomation(context.Context, Automation) (Automation, error)
	CreateRun(context.Context, AutomationRun) (AutomationRun, error)
	GetRun(context.Context, string, string) (AutomationRun, error)
	ListRuns(context.Context, RunFilter) ([]AutomationRun, error)
	UpdateRun(context.Context, AutomationRun) (AutomationRun, error)
	// ClaimRun atomically transitions a run from RunStatusQueued to the
	// supplied next value (the service sets Status=RunStatusClaiming and the
	// claim/runner identity before calling). It returns ErrClaimRace if the
	// persisted run is no longer Queued (another claimer won), ErrNotFound if
	// the run does not exist, and otherwise the written run. The read+guard+
	// write happens atomically under the store's own lock so the claim
	// transition is correct without an external mutex.
	ClaimRun(context.Context, AutomationRun) (AutomationRun, error)
	// UpdateRunIf conditionally applies next only when the persisted run's
	// Status and ClaimID match the expected RunCondition. It returns
	// ErrConditionMismatch if they diverged (a concurrent claim/state change
	// won), ErrNotFound if the run does not exist, and otherwise the written
	// run. The read+guard+write is atomic under the store's own lock so two
	// concurrent guarded writes cannot both apply. Reconcile terminal-write
	// guards use this to avoid clobbering a concurrently-claimed run.
	UpdateRunIf(context.Context, RunCondition, AutomationRun) (AutomationRun, error)
	CreateAttempt(context.Context, AutomationAttempt) (AutomationAttempt, error)
	CreateParallelBatch(context.Context, AutomationParallelBatch) (AutomationParallelBatch, error)
	GetParallelBatch(context.Context, string, string) (AutomationParallelBatch, error)
	// PurgeAutomationsByStatus permanently deletes automation nodes matching the
	// given project + status filter (e.g. "cancelled"). Used by
	// PurgeCancelledAutomations to clean up stale clutter that slows the
	// reconciler's claim-next scan. Returns the count of deleted nodes.
	// Associated runs/attempts are cleaned via relationship cascade in the
	// graph layer's DeleteNodes.
	PurgeAutomationsByStatus(context.Context, string, string) (int, error)
	// PurgeRunsByStatus permanently deletes automation-run nodes matching the
	// given project + status (e.g. "completed"). Used to clean up thousands of
	// historical terminal runs that slow the reconciler's claim-next scan.
	// Only terminal statuses may be purged. Returns the count deleted.
	PurgeRunsByStatus(context.Context, string, string) (int, error)
	// DeleteAutomation permanently removes a single automation definition node
	// by id. It does NOT remove that automation's runs (call
	// DeleteRunsByAutomation first). Scoped by project; idempotent (nil when
	// absent). Used by the governed chain-run delete cascade.
	DeleteAutomation(context.Context, string, string) error
	// DeleteRunsByAutomation permanently removes every run node owned by the
	// given automation id. Scoped by project. Returns the count removed.
	// Idempotent (0, nil) when none exist. Used by the governed chain-run
	// delete cascade.
	DeleteRunsByAutomation(context.Context, string, string) (int, error)
}

type SubmitRunInput

type SubmitRunInput struct {
	ProjectID         string   `json:"project_id,omitempty"`
	AutomationID      string   `json:"automation_id"`
	PlanID            string   `json:"plan_id,omitempty"`
	TaskID            string   `json:"task_id,omitempty"`
	OwnerAgent        string   `json:"owner_agent,omitempty"`
	RunnerKind        string   `json:"runner_kind,omitempty"`
	OrchestratorRunID string   `json:"orchestrator_run_id,omitempty"`
	ParentRunID       string   `json:"parent_run_id,omitempty"`
	EvidenceRefs      []string `json:"evidence_refs,omitempty"`
	VerifierRefs      []string `json:"verifier_result_refs,omitempty"`
	SafeNextAction    string   `json:"safe_next_action,omitempty"`
}

type UpdateAutomationStatusInput added in v0.2.2

type UpdateAutomationStatusInput struct {
	ProjectID    string `json:"project_id,omitempty"`
	AutomationID string `json:"automation_id"`
	Status       string `json:"status"`
	RunID        string `json:"run_id,omitempty"`
	TraceID      string `json:"trace_id,omitempty"`
}

type WorkPlanStatusTriggerOptions added in v0.2.1

type WorkPlanStatusTriggerOptions struct {
	Enabled  bool
	Statuses []string
}

type WorkTaskAPI

type WorkTaskAPI interface {
	GetWorkPlan(context.Context, string, string) (projectworkplan.WorkPlan, error)
	GetWorkTask(context.Context, string, string) (projectworkplan.WorkTask, error)
	ListWorkTasks(context.Context, projectworkplan.WorkTaskFilter) ([]projectworkplan.WorkTask, error)
	ListOpenWorkTasks(context.Context, projectworkplan.WorkTaskFilter) ([]projectworkplan.WorkTask, error)
	ClaimWorkTask(context.Context, projectworkplan.WorkTaskActionInput) (projectworkplan.WorkTask, error)
	StartWorkTask(context.Context, projectworkplan.WorkTaskActionInput) (projectworkplan.WorkTask, error)
	AttachEvidence(context.Context, projectworkplan.AttachInput) (projectworkplan.Attachment, error)
	AttachVerifierResult(context.Context, projectworkplan.AttachInput) (projectworkplan.Attachment, error)
	AttachReviewResult(context.Context, projectworkplan.AttachInput) (projectworkplan.Attachment, error)
	CompleteWorkTask(context.Context, projectworkplan.WorkTaskActionInput) (projectworkplan.WorkTask, error)
	FailWorkTask(context.Context, projectworkplan.WorkTaskActionInput) (projectworkplan.WorkTask, error)
	BlockWorkTask(context.Context, projectworkplan.WorkTaskActionInput) (projectworkplan.WorkTask, error)
	// ResetWorkTaskRecovery clears a task's replacement-retry-limit block. Used by
	// ForceCancelRun (FM-3) so that cancelling a terminal retry-limit run also
	// unblocks the parent task atomically, instead of leaving it blocked with a
	// stale TraceID and no recovery marker.
	ResetWorkTaskRecovery(context.Context, projectworkplan.WorkTaskActionInput) (projectworkplan.WorkTask, error)
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL