A statement made to a mind is now three things kept apart: the observation that it arrived, an append-only assertion (attributed actor, stance, certainty, modality, negation, reported or quoted speech, hypothetical status, interpretation confidence and notes), and, only for factual assertions, one piece of evidence for a proposition belief attributed to whoever actually stands behind it. Reported speech is attributed to the person reported and weighed as second-hand; hypotheticals, questions and imperatives create no belief; expressed doubt counts against; a possibly sarcastic remark is recorded with low interpretation confidence rather than guessed at. Interpretation is deterministic code; a model may enrich the claim list but never sets stance or confidence. Association no longer treats conversational turns as world events: hypotheticals, questions, requests and consecutive turns by one speaker cannot seed causal hypotheses. Adds internal/epistemics, migration 0018, the assertions API, assertions on belief pages and in belief detail, a shared end-to-end test harness (tests/harness), end-to-end tests for eight speech patterns, and updates the milestone test to the new provenance.
70 lines
1.7 KiB
Go
70 lines
1.7 KiB
Go
package api
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/andydixon/noema/internal/auth"
|
|
"github.com/andydixon/noema/internal/epistemics"
|
|
"github.com/andydixon/noema/pkg/ids"
|
|
)
|
|
|
|
// assertionRoutes expose who asserted what to a mind, kept apart from what
|
|
// the mind believes.
|
|
func (s *Server) assertionRoutes() {
|
|
m := s.mux
|
|
m.Handle("GET /api/v1/minds/{mind}/assertions", require(auth.RoleObserver, s.assertionsList))
|
|
m.Handle("GET /api/v1/minds/{mind}/assertions/actors", require(auth.RoleObserver, s.assertionActors))
|
|
}
|
|
|
|
func (s *Server) assertionsList(w http.ResponseWriter, r *http.Request) {
|
|
m, ok := s.mind(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
q := r.URL.Query()
|
|
f := epistemics.Filter{AttributedTo: q.Get("actor"), Stance: q.Get("stance"), Limit: intParam(q.Get("limit"), 100)}
|
|
if b := q.Get("belief"); b != "" {
|
|
id, err := ids.Parse(b)
|
|
if err != nil {
|
|
fail(w, r, ValidationError("bad belief id"))
|
|
return
|
|
}
|
|
f.BeliefID = id
|
|
}
|
|
list, err := s.deps.Assertions.List(r.Context(), m.ID, f)
|
|
if err != nil {
|
|
fail(w, r, err)
|
|
return
|
|
}
|
|
if list == nil {
|
|
list = []epistemics.Assertion{}
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{"items": list})
|
|
}
|
|
|
|
func (s *Server) assertionActors(w http.ResponseWriter, r *http.Request) {
|
|
m, ok := s.mind(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
list, err := s.deps.Assertions.Actors(r.Context(), m.ID)
|
|
if err != nil {
|
|
fail(w, r, err)
|
|
return
|
|
}
|
|
if list == nil {
|
|
list = []epistemics.ActorSummary{}
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{"items": list})
|
|
}
|
|
|
|
// intParam parses a bounded positive integer query value.
|
|
func intParam(v string, def int) int {
|
|
n, err := strconv.Atoi(v)
|
|
if err != nil || n <= 0 {
|
|
return def
|
|
}
|
|
return n
|
|
}
|