feat(epistemics): record assertions between observations and beliefs
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.
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user