ed491e23fc
The review comment asked for basic DI. The answer is the idiom voice.go already had for capabilities — a cohesive *Wiring struct — applied to a group that is not a capability toggle, plus the decision written down so it is a rule and not a habit. recallWiring holds the embedder, the vector store, the personal boundary and the two numbers that gate an answer. They sat in three places on reactiveHandler, with the gate numbers a hundred lines from the store they gate. Its zero value means no recall, so it is a value, not a pointer like the optional-capability groups. dataStore stays out of it. patterns.go, ecosystem_acts.go and confirm.go use it, so it is not part of this cluster. docs/handler-wiring.md records the choice, rejects a container or a wire-style generator outright, defers narrow per-handler interfaces to the package split that would justify them, and states the constraint the task named: a wiring change does not ride a feature PR. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
55 lines
1.6 KiB
Go
55 lines
1.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/kami/maven/internal/ipc"
|
|
"github.com/kami/maven/internal/memory"
|
|
"github.com/kami/maven/internal/router"
|
|
"github.com/kami/maven/internal/tool"
|
|
"github.com/kami/maven/internal/voice"
|
|
)
|
|
|
|
// TestApplyAction_FactCapture_QueuesEntityResolution covers the wiring from
|
|
// Vikunja #279: a voice-tapped fact's key doubles as its entity-resolution
|
|
// subject, so writing a fact through applyAction (not directly through the
|
|
// store) must leave it in the fact-enrichment pending queue.
|
|
func TestApplyAction_FactCapture_QueuesEntityResolution(t *testing.T) {
|
|
ctx := context.Background()
|
|
st := newTestStore(t)
|
|
api := ipc.NewStoreAPI(st)
|
|
now := time.Now()
|
|
|
|
emb := router.NewHashEmbedder(1024)
|
|
matcher := tool.NewMatcher(api)
|
|
rtr := buildRouter(emb, matcher, 0.55, nil)
|
|
|
|
h := &reactiveHandler{
|
|
api: api,
|
|
recall: recallWiring{embedder: emb, memStore: memory.NewInMemoryStore()},
|
|
router: rtr,
|
|
replier: voice.NewStubReplier(),
|
|
now: func() time.Time { return now },
|
|
dataStore: st,
|
|
}
|
|
|
|
dec := router.Decision{
|
|
Intent: router.IntentFact,
|
|
Slots: router.Slots{Key: "the espresso machine", HasKey: true, Value: `"needs descaling"`},
|
|
}
|
|
h.applyAction(ctx, dec)
|
|
|
|
pending, err := st.PendingFactResolutions(ctx, 10)
|
|
if err != nil {
|
|
t.Fatalf("PendingFactResolutions: %v", err)
|
|
}
|
|
if len(pending) != 1 {
|
|
t.Fatalf("expected 1 fact queued for entity resolution, got %d: %+v", len(pending), pending)
|
|
}
|
|
if pending[0].Subject != "the espresso machine" {
|
|
t.Fatalf("expected subject to carry the fact key, got %q", pending[0].Subject)
|
|
}
|
|
}
|