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:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user