Phase 8 follow-up: resolve the child named in the question
The Hub forwards `person` equal to the asker (`PersonOrDefault`), so the spoken question "what does Harry have on today at school?" arrives with person "sam" and returned "no data for that person" (found by the Hub agent on AUD-116). resolveChild now falls back to the question text when the requested person is not one of this project's children: whole-word, case-insensitive match against the child names this service already publishes in /capabilities. An explicit person still wins, two named children still return answer:null, and the permission check still runs on the resolved child. No model, no free text. Live end-to-end through the Hub, with no person field: POST http://192.168.20.13:8788/ask {"text":"what does Harry have on today at school?","asker":"sam","source":"console"} 200 {"status":"ok", "answer":"Harry has 8 school events today from 8:55am to 6:00pm and 6 overdue tasks.", "route":{"kind":"project","project":"compass_nhs"}} Image 0.6.1-phase8; docs/phase-8-hub-notes.md records the fallback and the end-to-end transcript. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -142,6 +142,48 @@ func TestHubCapabilitiesAndAsk(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestHubAskResolvesTheNamedChild covers the Hub's actual call shape: the Hub
|
||||
// sends `person` equal to the asker (`PersonOrDefault`), so the child's name is
|
||||
// only in the question text. The literal phase question must still answer.
|
||||
func TestHubAskResolvesTheNamedChild(t *testing.T) {
|
||||
ts, st := newTestServerHub(t, testAppriseConfig("", "", ""), testHubToken)
|
||||
loc := mustLoc(t)
|
||||
ctx := context.Background()
|
||||
harry, _ := st.ChildByName(ctx, "Harry")
|
||||
now := time.Now().In(loc)
|
||||
today := time.Date(now.Year(), now.Month(), now.Day(), 9, 0, 0, 0, loc)
|
||||
addHubItem(t, st, harry.ID, store.Item{
|
||||
Type: "event", Subject: "11XMUSA6", Teacher: "JBR",
|
||||
Title: "1 - 11XMUSA6 - A1 - JBR", DueAt: &today,
|
||||
})
|
||||
if _, err := st.RecordScan(ctx, &harry.ID, 5, 1, true, ""); err != nil {
|
||||
t.Fatalf("record scan: %v", err)
|
||||
}
|
||||
|
||||
resp, raw := hubJSON(t, ts, http.MethodPost, "/ask", testHubToken,
|
||||
`{"question":"what does Harry have on today at school?","asker":"sam","person":"sam","source":"console"}`)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("ask status = %d, body = %s", resp.StatusCode, raw)
|
||||
}
|
||||
var ask struct {
|
||||
Answer *string `json:"answer"`
|
||||
Reason string `json:"reason"`
|
||||
Data map[string]any `json:"data"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &ask); err != nil {
|
||||
t.Fatalf("ask json: %v (%s)", err, raw)
|
||||
}
|
||||
if ask.Answer == nil {
|
||||
t.Fatalf("answer = nil, reason = %q", ask.Reason)
|
||||
}
|
||||
if ask.Data["child"] != "Harry" {
|
||||
t.Errorf("data.child = %v, want Harry", ask.Data["child"])
|
||||
}
|
||||
if !strings.Contains(*ask.Answer, "school event") {
|
||||
t.Errorf("answer = %q, want the lesson count", *ask.Answer)
|
||||
}
|
||||
}
|
||||
|
||||
// TestHubAskReadsOnly is the contract's first rule made testable: /ask and
|
||||
// /capabilities must not write anything (AGENTS.md §10 rule 5).
|
||||
func TestHubAskReadsOnly(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user