26f897c1ec
release / release-please (push) Failing after 1m49s
docs / build (push) Failing after 6m34s
release / build-and-upload (arm64, linux) (push) Has been cancelled
release / build-and-upload (arm64, windows) (push) Has been cancelled
release / build-darwin (amd64, darwin) (push) Has been cancelled
release / checksums (push) Has been cancelled
release / finalize (push) Has been cancelled
release / build-darwin (arm64, darwin) (push) Has been cancelled
release / build-and-upload (amd64, linux) (push) Has been cancelled
release / build-and-upload (amd64, windows) (push) Has been cancelled
docs / deploy (push) Has been cancelled
209 lines
7.7 KiB
Go
209 lines
7.7 KiB
Go
package db
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
|
|
"github.com/kunchenguid/no-mistakes/internal/types"
|
|
)
|
|
|
|
// StepResult represents the result of a pipeline step execution.
|
|
type StepResult struct {
|
|
ID string
|
|
RunID string
|
|
StepName types.StepName
|
|
StepOrder int
|
|
Status types.StepStatus
|
|
ExitCode *int
|
|
DurationMS *int64
|
|
LogPath *string
|
|
FindingsJSON *string
|
|
Error *string
|
|
StartedAt *int64
|
|
CompletedAt *int64
|
|
LastActivityAt *int64
|
|
LastActivity *string
|
|
AgentPID *int
|
|
AutoFixLimit *int
|
|
}
|
|
|
|
const stepResultColumns = `id, run_id, step_name, step_order, status, exit_code, duration_ms, log_path, findings_json, error, started_at, completed_at, last_activity_at, last_activity, agent_pid, auto_fix_limit`
|
|
|
|
// InsertStepResult creates a new step result record.
|
|
func (d *DB) InsertStepResult(runID string, stepName types.StepName) (*StepResult, error) {
|
|
s := &StepResult{
|
|
ID: newID(),
|
|
RunID: runID,
|
|
StepName: stepName,
|
|
StepOrder: stepName.Order(),
|
|
Status: types.StepStatusPending,
|
|
}
|
|
_, err := d.sql.Exec(
|
|
`INSERT INTO step_results (id, run_id, step_name, step_order, status) VALUES (?, ?, ?, ?, ?)`,
|
|
s.ID, s.RunID, s.StepName, s.StepOrder, s.Status,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("insert step result: %w", err)
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
// GetStepResult returns a step result by ID.
|
|
func (d *DB) GetStepResult(id string) (*StepResult, error) {
|
|
s := &StepResult{}
|
|
err := d.sql.QueryRow(
|
|
`SELECT `+stepResultColumns+` FROM step_results WHERE id = ?`, id,
|
|
).Scan(&s.ID, &s.RunID, &s.StepName, &s.StepOrder, &s.Status, &s.ExitCode, &s.DurationMS, &s.LogPath, &s.FindingsJSON, &s.Error, &s.StartedAt, &s.CompletedAt, &s.LastActivityAt, &s.LastActivity, &s.AgentPID, &s.AutoFixLimit)
|
|
if err == sql.ErrNoRows {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get step result: %w", err)
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
// GetStepsByRun returns all step results for a run, in execution order.
|
|
func (d *DB) GetStepsByRun(runID string) ([]*StepResult, error) {
|
|
rows, err := d.sql.Query(
|
|
`SELECT `+stepResultColumns+` FROM step_results WHERE run_id = ? ORDER BY step_order`, runID,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get steps by run: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
var steps []*StepResult
|
|
for rows.Next() {
|
|
s := &StepResult{}
|
|
if err := rows.Scan(&s.ID, &s.RunID, &s.StepName, &s.StepOrder, &s.Status, &s.ExitCode, &s.DurationMS, &s.LogPath, &s.FindingsJSON, &s.Error, &s.StartedAt, &s.CompletedAt, &s.LastActivityAt, &s.LastActivity, &s.AgentPID, &s.AutoFixLimit); err != nil {
|
|
return nil, fmt.Errorf("scan step result: %w", err)
|
|
}
|
|
steps = append(steps, s)
|
|
}
|
|
return steps, rows.Err()
|
|
}
|
|
|
|
// UpdateStepStatus updates a step's status.
|
|
func (d *DB) UpdateStepStatus(id string, status types.StepStatus) error {
|
|
_, err := d.sql.Exec(`UPDATE step_results SET status = ?, last_activity_at = ?, last_activity = ? WHERE id = ?`, status, now(), fmt.Sprintf("status: %s", status), id)
|
|
if err != nil {
|
|
return fmt.Errorf("update step status: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UpdateStepStatusWithDuration updates a step's status and execution duration together.
|
|
func (d *DB) UpdateStepStatusWithDuration(id string, status types.StepStatus, durationMS int64) error {
|
|
_, err := d.sql.Exec(`UPDATE step_results SET status = ?, duration_ms = ?, last_activity_at = ?, last_activity = ? WHERE id = ?`, status, durationMS, now(), fmt.Sprintf("status: %s", status), id)
|
|
if err != nil {
|
|
return fmt.Errorf("update step status with duration: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// StartStep marks a step as running with a started_at timestamp.
|
|
func (d *DB) StartStep(id string) error {
|
|
return d.StartStepWithAutoFixLimit(id, 0)
|
|
}
|
|
|
|
// StartStepWithAutoFixLimit marks a step as running and records the effective
|
|
// auto-fix limit that status surfaces use while the step is active.
|
|
func (d *DB) StartStepWithAutoFixLimit(id string, autoFixLimit int) error {
|
|
ts := now()
|
|
_, err := d.sql.Exec(`UPDATE step_results SET status = ?, started_at = ?, last_activity_at = ?, last_activity = ?, agent_pid = NULL, auto_fix_limit = ? WHERE id = ?`, types.StepStatusRunning, ts, ts, "step started", autoFixLimitDBValue(autoFixLimit), id)
|
|
if err != nil {
|
|
return fmt.Errorf("start step: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *DB) SetStepAutoFixLimit(id string, autoFixLimit int) error {
|
|
if _, err := d.sql.Exec(`UPDATE step_results SET auto_fix_limit = ? WHERE id = ?`, autoFixLimitDBValue(autoFixLimit), id); err != nil {
|
|
return fmt.Errorf("set step auto-fix limit: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func autoFixLimitDBValue(autoFixLimit int) any {
|
|
if autoFixLimit <= 0 {
|
|
return nil
|
|
}
|
|
return autoFixLimit
|
|
}
|
|
|
|
// CompleteStep marks a step as completed with timing and result info.
|
|
func (d *DB) CompleteStep(id string, exitCode int, durationMS int64, logPath string) error {
|
|
return d.CompleteStepWithStatus(id, types.StepStatusCompleted, exitCode, durationMS, logPath)
|
|
}
|
|
|
|
// CompleteStepWithStatus marks a step as finished with timing and result info.
|
|
func (d *DB) CompleteStepWithStatus(id string, status types.StepStatus, exitCode int, durationMS int64, logPath string) error {
|
|
_, err := d.sql.Exec(
|
|
`UPDATE step_results SET status = ?, exit_code = ?, duration_ms = ?, log_path = ?, completed_at = ?, last_activity_at = ?, last_activity = ?, agent_pid = NULL WHERE id = ?`,
|
|
status, exitCode, durationMS, logPath, now(), now(), fmt.Sprintf("status: %s", status), id,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("complete step: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// FailStep marks a step as failed with an error message and duration.
|
|
func (d *DB) FailStep(id string, errMsg string, durationMS int64) error {
|
|
_, err := d.sql.Exec(
|
|
`UPDATE step_results SET status = ?, error = ?, duration_ms = ?, completed_at = ?, last_activity_at = ?, last_activity = ?, agent_pid = NULL WHERE id = ?`,
|
|
types.StepStatusFailed, errMsg, durationMS, now(), now(), "step failed: "+errMsg, id,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("fail step: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// TouchStepActivity records the latest meaningful activity for an active step
|
|
// without changing its status or current agent pid.
|
|
func (d *DB) TouchStepActivity(id string, text string) error {
|
|
_, err := d.sql.Exec(`UPDATE step_results SET last_activity_at = ?, last_activity = ? WHERE id = ?`, now(), text, id)
|
|
if err != nil {
|
|
return fmt.Errorf("touch step activity: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetStepAgentActivity records an agent lifecycle activity and replaces the
|
|
// active agent pid. Passing nil clears the pid after the process exits.
|
|
func (d *DB) SetStepAgentActivity(id string, text string, agentPID *int) error {
|
|
_, err := d.sql.Exec(`UPDATE step_results SET last_activity_at = ?, last_activity = ?, agent_pid = ? WHERE id = ?`, now(), text, agentPID, id)
|
|
if err != nil {
|
|
return fmt.Errorf("set step agent activity: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetStepDuration sets the execution-only duration on a step result.
|
|
func (d *DB) SetStepDuration(id string, durationMS int64) error {
|
|
_, err := d.sql.Exec(`UPDATE step_results SET duration_ms = ? WHERE id = ?`, durationMS, id)
|
|
if err != nil {
|
|
return fmt.Errorf("set step duration: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetStepFindings sets the findings JSON on a step result.
|
|
func (d *DB) SetStepFindings(id string, findingsJSON string) error {
|
|
_, err := d.sql.Exec(`UPDATE step_results SET findings_json = ? WHERE id = ?`, findingsJSON, id)
|
|
if err != nil {
|
|
return fmt.Errorf("set step findings: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ClearStepFindings removes any stored findings JSON from a step result.
|
|
func (d *DB) ClearStepFindings(id string) error {
|
|
_, err := d.sql.Exec(`UPDATE step_results SET findings_json = NULL WHERE id = ?`, id)
|
|
if err != nil {
|
|
return fmt.Errorf("clear step findings: %w", err)
|
|
}
|
|
return nil
|
|
}
|