750b7abcd5
- .claude/CLAUDE.md - .claude/agents/fn-recopilador/SKILL.md - .claude/rules/INDEX.md - .claude/rules/cpp_apps.md - bash/functions/infra/build_cpp_windows.sh - cpp/CMakeLists.txt - cpp/PATTERNS.md - cpp/framework/app_base.cpp - cpp/framework/app_base.h - dev/issues/README.md - ... Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
106 lines
3.2 KiB
Go
106 lines
3.2 KiB
Go
package infra
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
_ "github.com/mattn/go-sqlite3"
|
|
)
|
|
|
|
// ProposalFromFailure creates a proposal row in registry.db for each failed
|
|
// CheckResult. It opens the database at registryDB, filters results with
|
|
// Status=="fail", and inserts one proposal per failure using:
|
|
// - kind="new_function" for severity=="critical" checks (highest urgency proxy)
|
|
// - kind="improve_function" for severity=="warning" checks
|
|
//
|
|
// Note: the proposals table kind constraint only allows
|
|
// (new_function, new_type, improve_function, improve_type, new_pipeline).
|
|
// Until a dedicated "bug" kind is added, we use new_function/improve_function
|
|
// as the closest proxies for critical and warning failures respectively.
|
|
//
|
|
// Returns the list of proposal IDs created, or an error if the DB cannot be
|
|
// opened or any INSERT fails.
|
|
func ProposalFromFailure(registryDB string, appID string, results []CheckResult, executionID string) ([]string, error) {
|
|
db, err := SQLiteOpen(registryDB, "")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("proposal_from_failure: open registry db: %w", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
var created []string
|
|
now := time.Now().UTC().Format(time.RFC3339)
|
|
|
|
for _, r := range results {
|
|
if r.Status != "fail" {
|
|
continue
|
|
}
|
|
|
|
propID, err := generatePropID()
|
|
if err != nil {
|
|
return created, fmt.Errorf("proposal_from_failure: generate id: %w", err)
|
|
}
|
|
|
|
kind := proposalKind(r.Severity)
|
|
title := fmt.Sprintf("e2e fail: %s::%s", appID, r.ID)
|
|
desc := buildDescription(r)
|
|
|
|
evidence, _ := json.Marshal(map[string]any{
|
|
"check_id": r.ID,
|
|
"execution_id": executionID,
|
|
"exit_code": r.ExitCode,
|
|
"error": r.Error,
|
|
"severity": r.Severity,
|
|
})
|
|
|
|
_, err = db.Exec(`
|
|
INSERT INTO proposals (id, kind, target_id, title, description, evidence, status, created_by, reviewed_by, created_at, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, 'pending', 'reactive_loop', '', ?, ?)`,
|
|
propID, kind, appID, title, desc, string(evidence), now, now,
|
|
)
|
|
if err != nil {
|
|
return created, fmt.Errorf("proposal_from_failure: insert proposal %s: %w", propID, err)
|
|
}
|
|
created = append(created, propID)
|
|
}
|
|
|
|
return created, nil
|
|
}
|
|
|
|
// proposalKind maps check severity to an allowed proposals.kind value.
|
|
// critical -> new_function (highest urgency proxy)
|
|
// warning -> improve_function
|
|
func proposalKind(severity string) string {
|
|
if severity == "warning" {
|
|
return "improve_function"
|
|
}
|
|
return "new_function"
|
|
}
|
|
|
|
// buildDescription assembles a human-readable description for the proposal.
|
|
func buildDescription(r CheckResult) string {
|
|
desc := fmt.Sprintf("E2E check %q failed (severity: %s, exit_code: %d).", r.ID, r.Severity, r.ExitCode)
|
|
if r.Error != "" {
|
|
desc += "\n\nError: " + r.Error
|
|
}
|
|
if r.Stdout != "" {
|
|
desc += "\n\nStdout:\n" + r.Stdout
|
|
}
|
|
if r.Stderr != "" {
|
|
desc += "\n\nStderr:\n" + r.Stderr
|
|
}
|
|
desc += "\n\nSugerencia: revisar el comando/endpoint del check y el estado del servicio."
|
|
return desc
|
|
}
|
|
|
|
// generatePropID generates a random proposal ID of the form "prop_<16hexchars>".
|
|
func generatePropID() (string, error) {
|
|
b := make([]byte, 8)
|
|
if _, err := rand.Read(b); err != nil {
|
|
return "", fmt.Errorf("crypto/rand: %w", err)
|
|
}
|
|
return "prop_" + hex.EncodeToString(b), nil
|
|
}
|