feat: executions, assertions y bucle reactivo en fn_operations

Añade Execution, Assertion, AssertionResult al paquete fn_operations.
Motor de evaluación de assertions con reescritura SQL automática.
Bucle reactivo: ExecuteAndReact evalúa assertions y cambia status de
entities (corrupted/stale) + auto-crea proposals en registry.
CLI fn ops: assertion (add/list/show/delete/eval) y execution (add/list/show).
Migración 002_executions_assertions.sql con FTS para assertions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-28 17:13:37 +01:00
parent 8d98faccd9
commit 9ba1f86c34
11 changed files with 2230 additions and 87 deletions
+94
View File
@@ -130,6 +130,100 @@ func ValidateRelationInputs(inputs []RelationInput, knownEntities map[string]boo
return nil
}
// ValidateExecution checks execution integrity rules.
func ValidateExecution(e *Execution) *ValidationError {
var errs []string
if e.ID == "" {
errs = append(errs, "id is required")
}
if e.PipelineID == "" {
errs = append(errs, "pipeline_id is required")
}
switch e.Status {
case ExecSuccess, ExecFailure, ExecPartial:
case "":
errs = append(errs, "status is required")
default:
errs = append(errs, fmt.Sprintf("invalid status: %s", e.Status))
}
if e.StartedAt.IsZero() {
errs = append(errs, "started_at is required")
}
if len(errs) > 0 {
return &ValidationError{ID: e.ID, Errors: errs}
}
return nil
}
// ValidateAssertion checks assertion integrity rules.
func ValidateAssertion(a *Assertion, knownEntities map[string]bool) *ValidationError {
var errs []string
if a.ID == "" {
errs = append(errs, "id is required")
}
if a.EntityID == "" {
errs = append(errs, "entity_id is required")
} else if knownEntities != nil && !knownEntities[a.EntityID] {
errs = append(errs, fmt.Sprintf("entity_id references unknown entity: %s", a.EntityID))
}
if a.Name == "" {
errs = append(errs, "name is required")
}
if a.Kind == "" {
errs = append(errs, "kind is required")
}
if a.Rule == "" {
errs = append(errs, "rule is required")
}
switch a.Severity {
case SeverityCritical, SeverityWarning, SeverityInfo:
case "":
errs = append(errs, "severity is required")
default:
errs = append(errs, fmt.Sprintf("invalid severity: %s", a.Severity))
}
if len(errs) > 0 {
return &ValidationError{ID: a.ID, Errors: errs}
}
return nil
}
// ValidateAssertionResult checks assertion result integrity.
func ValidateAssertionResult(ar *AssertionResult) *ValidationError {
var errs []string
if ar.ID == "" {
errs = append(errs, "id is required")
}
if ar.AssertionID == "" {
errs = append(errs, "assertion_id is required")
}
switch ar.Status {
case ResultPass, ResultFail, ResultSkip:
case "":
errs = append(errs, "status is required")
default:
errs = append(errs, fmt.Sprintf("invalid status: %s", ar.Status))
}
if ar.EvaluatedAt.IsZero() {
errs = append(errs, "evaluated_at is required")
}
if len(errs) > 0 {
return &ValidationError{ID: ar.ID, Errors: errs}
}
return nil
}
// DetectCycle checks if adding a causal relation (from -> to) creates a cycle.
// Only considers relations where via != "" (causal/transformational).
// Semantic relations (via == "") are exempt from cycle detection.