loop: add rule trace/explanation engine
ExplainTick and ExplainGate produce a full TickTrace for every tick, recording per-rule: predicate result, gate result, first gate blocker, gate detail (snooze/cooldown/presence etc.), and win/loss info. IPC layer: new MethodTickTrace, DTOs (TickTrace, RuleTrace, GateDetail), dispatch, client proxy, and CoreAPI interface method. Daemon: tickLoop caches the latest trace; daemonAPI wraps storeAPI with a TickTrace override that returns the cached trace. Tests: 15 new tests for ExplainGate (all blockers, bypass conditions, ordering) and ExplainTick (nothing fires, one fires, tiebreak, winner/lost_to recording, gate-blocked recording).
This commit is contained in:
@@ -240,6 +240,42 @@ type CoreAPI interface {
|
||||
LookupTool(ctx context.Context, name string) (Tool, error)
|
||||
ListTools(ctx context.Context, status string) ([]Tool, error)
|
||||
RevertFact(ctx context.Context, key string) (int64, error)
|
||||
|
||||
// TickTrace returns the most recent tick's rule trace. The daemon caches
|
||||
// this after every tick; the store adapter returns an error (trace is not
|
||||
// persisted — it's a daemon-level cache).
|
||||
TickTrace(ctx context.Context) (TickTrace, error)
|
||||
}
|
||||
|
||||
// --- Rule trace / explanation DTOs ---
|
||||
|
||||
// RuleTrace — per-rule evaluation result for one tick.
|
||||
type RuleTrace struct {
|
||||
RuleName string `json:"rule_name"`
|
||||
Severity int `json:"severity"`
|
||||
PredicateResult bool `json:"predicate_result"`
|
||||
GateResult bool `json:"gate_result"`
|
||||
GateBlockedBy string `json:"gate_blocked_by,omitempty"`
|
||||
GateDetail GateDetail `json:"gate_detail,omitempty"`
|
||||
WasSelected bool `json:"was_selected"`
|
||||
LostTo string `json:"lost_to,omitempty"`
|
||||
}
|
||||
|
||||
// GateDetail — snapshot of the values the gate checked.
|
||||
type GateDetail struct {
|
||||
SnoozeUntil *time.Time `json:"snooze_until,omitempty"`
|
||||
CooldownUntil *time.Time `json:"cooldown_until,omitempty"`
|
||||
QuietHours bool `json:"quiet_hours"`
|
||||
CalendarBusy bool `json:"calendar_busy"`
|
||||
Presence string `json:"presence"`
|
||||
InertKeysMissing []string `json:"inert_keys_missing,omitempty"`
|
||||
}
|
||||
|
||||
// TickTrace — snapshot of one tick's rule evaluations.
|
||||
type TickTrace struct {
|
||||
Now time.Time `json:"now"`
|
||||
Winner string `json:"winner"`
|
||||
Rules []RuleTrace `json:"rules"`
|
||||
}
|
||||
|
||||
// ErrToolNotFound — no tool row with this name (re-exported store sentinel for
|
||||
|
||||
@@ -345,6 +345,14 @@ func (c *Client) ListTools(ctx context.Context, status string) ([]Tool, error) {
|
||||
return r.Tools, nil
|
||||
}
|
||||
|
||||
func (c *Client) TickTrace(ctx context.Context) (TickTrace, error) {
|
||||
var t TickTrace
|
||||
if err := c.call(ctx, MethodTickTrace, nil, &t); err != nil {
|
||||
return TickTrace{}, err
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (c *Client) RevertFact(ctx context.Context, key string) (int64, error) {
|
||||
var result struct {
|
||||
NewID int64 `json:"new_id"`
|
||||
|
||||
@@ -174,6 +174,10 @@ func (a *storeAPI) RevertFact(ctx context.Context, key string) (int64, error) {
|
||||
return newID, mapErr(err)
|
||||
}
|
||||
|
||||
func (a *storeAPI) TickTrace(ctx context.Context) (TickTrace, error) {
|
||||
return TickTrace{}, errors.New("store: tick trace not available via direct store API")
|
||||
}
|
||||
|
||||
func (a *storeAPI) ListTools(ctx context.Context, status string) ([]Tool, error) {
|
||||
ts, err := a.s.ListTools(ctx, status)
|
||||
if err != nil {
|
||||
@@ -629,6 +633,13 @@ func (s *Server) dispatch(ctx context.Context, req Request) (json.RawMessage, er
|
||||
}
|
||||
return marshalResult(map[string]int64{"new_id": newID}), nil
|
||||
|
||||
case MethodTickTrace:
|
||||
t, err := s.api.TickTrace(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return marshalResult(t), nil
|
||||
|
||||
case MethodAssertStepUp:
|
||||
if s.StepUp != nil {
|
||||
return marshalResult(nil), s.StepUp(ctx)
|
||||
|
||||
@@ -35,6 +35,7 @@ const (
|
||||
MethodLookupTool Method = "lookup_tool"
|
||||
MethodListTools Method = "list_tools"
|
||||
MethodRevertFact Method = "revert_fact"
|
||||
MethodTickTrace Method = "tick_trace"
|
||||
)
|
||||
|
||||
// Request — one frame from module to core. Params is the JSON-encoded argument
|
||||
|
||||
Reference in New Issue
Block a user