Files
fn_registry/functions/infra/proposal_from_failure.go
egutierrez 750b7abcd5 chore: auto-commit (97 archivos)
- .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>
2026-05-09 18:11:24 +02:00

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
}