66c578a6f4
Introduce ActionValidationStatus enum (valid, unresolved, missing_argument, invalid_argument, ambiguous_target) as the typed classification of validation outcomes. ActionValidationResult now carries Status instead of boolean flags. Backward-compatible: Unresolved() and Valid() methods preserved on the result. Existing validation behavior unchanged: only blank Fn produces invalid_argument. All downstream behavior (proposeGap, confirmation, task_status, praxis, hexis) unchanged. Tests added for all five status values, backward compatibility, and the full validation → execution boundary.
93 lines
2.6 KiB
Go
93 lines
2.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/kami/maven/internal/decision"
|
|
"github.com/kami/maven/internal/router"
|
|
)
|
|
|
|
// resolveAction produces an ActionCandidate from a routing decision. It is the
|
|
// single boundary between routing and action execution: everything downstream
|
|
// (refusesCommand, task-status, Praxis, Hexis, proposeGap, tool.Executor.Exec)
|
|
// consumes the candidate rather than re-resolving the function.
|
|
//
|
|
// Delegates to router.ResolveActionCandidate for the resolution logic, then
|
|
// records the outcome in the decision trace.
|
|
func (h *reactiveHandler) resolveAction(ctx context.Context, dec router.Decision) router.ActionCandidate {
|
|
candidate := router.ResolveActionCandidate(dec, h.matcher)
|
|
|
|
// Record the resolution outcome in the decision trace.
|
|
if dec.Intent == router.IntentAct {
|
|
if candidate.ActionResolved() {
|
|
noteActionResolution(ctx, string(candidate.Source), candidate.Fn, true)
|
|
} else {
|
|
noteActionResolution(ctx, "matcher", "", false)
|
|
}
|
|
}
|
|
|
|
return candidate
|
|
}
|
|
|
|
// noteActionResolution records the action resolution outcome in the decision
|
|
// trace. A nil recorder is the normal case in tests.
|
|
func noteActionResolution(ctx context.Context, source, fn string, resolved bool) {
|
|
rec := decision.From(ctx)
|
|
if rec == nil {
|
|
return
|
|
}
|
|
outcome := decision.Declined
|
|
reason := "no match"
|
|
if resolved {
|
|
outcome = decision.Won
|
|
reason = "resolved via " + source
|
|
if fn != "" {
|
|
reason += ": " + fn
|
|
}
|
|
}
|
|
rec.Note(decision.Claim{
|
|
Stage: decision.StageAction,
|
|
Claimant: "action-resolve",
|
|
Outcome: outcome,
|
|
Reason: reason,
|
|
})
|
|
}
|
|
|
|
// noteActionValidation records the structural validation outcome in the
|
|
// decision trace. Five outcomes: unresolved (matcher miss), valid
|
|
// (structurally admissible), invalid_argument, missing_argument, or
|
|
// ambiguous_target (structurally malformed).
|
|
func noteActionValidation(ctx context.Context, v router.ActionValidationResult) {
|
|
rec := decision.From(ctx)
|
|
if rec == nil {
|
|
return
|
|
}
|
|
switch v.Status {
|
|
case router.ActionUnresolved:
|
|
rec.Note(decision.Claim{
|
|
Stage: decision.StageAction,
|
|
Claimant: "action-validation",
|
|
Outcome: decision.Declined,
|
|
Reason: "unresolved",
|
|
})
|
|
case router.ActionValid:
|
|
rec.Note(decision.Claim{
|
|
Stage: decision.StageAction,
|
|
Claimant: "action-validation",
|
|
Outcome: decision.Won,
|
|
Reason: "valid",
|
|
})
|
|
default:
|
|
reason := string(v.Status)
|
|
if len(v.Issues) > 0 {
|
|
reason = string(v.Status) + ":" + v.Issues[0].Reason
|
|
}
|
|
rec.Note(decision.Claim{
|
|
Stage: decision.StageAction,
|
|
Claimant: "action-validation",
|
|
Outcome: decision.Declined,
|
|
Reason: reason,
|
|
})
|
|
}
|
|
}
|