Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/modules/ai/handler.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 41000ad30fff4129fdaf5ec84e15ba14c27d4890 |
| Branch | m5-price-to-win |
| Path or archive member | internal/modules/ai/handler.go |
| Original SHA-256 | c037f51464f96694ed8f80f0fa69722dfe63a57020de44821f438eb3705a6b7e |
| Git blob SHA | a671444221436821d90c72ad016b5a5ae5b856fd |
| Reading-copy SHA-256 | c037f51464f96694ed8f80f0fa69722dfe63a57020de44821f438eb3705a6b7e |
Every recorded occurrence
These 17 records contain identical original source bytes. Their paths and revisions remain part of the archive.
| Original record | Branch or archive | Revision / identifier | Original location |
|---|---|---|---|
| Janady13/missionmatch / internal/modules/ai/handler.go | m5-price-to-win | 41000ad30fff4129fdaf5ec84e15ba14c27d4890 | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | collision-deconfliction | 4ee5a09972608ed89f8377c6665542d5f450f0a6 | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | graphrag-foundation | b0ddeb9f633fd138d09309d1a17b7d13bc3a67f7 | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | time-to-fill-survival | b66e1e505863d4f17741a4b9ede73509e45bd6c6 | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | mag-substrate | b82c7100a492dec67dfd1eda6b3f53def859be35 | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | capture-readiness | bae6af7cb2997e819174a9e9cfff5e16067a1975 | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | phase2-connectors | e038d4c687aec16f8bd9bc4d907eea14d91f8704 | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | presentation-readiness | eb574acaa29d71ce4d0f852572e87c275c79fdb7 | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | vendor-intelligence | fe9796a19c261951d3f2565206c0c1b5ac294465 | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | collision-deconfliction | 4ee5a09972608ed89f8377c6665542d5f450f0a6 | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | m5-price-to-win | 41000ad30fff4129fdaf5ec84e15ba14c27d4890 | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | mag-substrate | b82c7100a492dec67dfd1eda6b3f53def859be35 | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | time-to-fill-survival | b66e1e505863d4f17741a4b9ede73509e45bd6c6 | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | graphrag-foundation | b0ddeb9f633fd138d09309d1a17b7d13bc3a67f7 | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | presentation-readiness | eb574acaa29d71ce4d0f852572e87c275c79fdb7 | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | phase2-connectors | e038d4c687aec16f8bd9bc4d907eea14d91f8704 | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | capture-readiness | bae6af7cb2997e819174a9e9cfff5e16067a1975 | Open original |
Recovered source transcript
Download complete source text ↓162 lines · 6.4 KB
package ai
import (
"encoding/json"
"errors"
"net/http"
"strings"
"github.com/go-chi/chi/v5"
"github.com/missionworkforce/missionmatch/internal/platform/inference"
"github.com/missionworkforce/missionmatch/internal/platform/security"
"github.com/missionworkforce/missionmatch/internal/platform/tenant"
)
// Handler mounts the AI brain under the tenant scoped group, behind the session resolver, CSRF, RBAC, rate
// limit, and audit (the same chain as every other module). It exposes one endpoint that routes a natural
// language question across the augmented generation strategies and returns an explainable, faithfulness
// gated, tenant isolated answer. The deterministic engine remains the floor; the brain narrates, it never
// scores.
type Handler struct {
orch *Orchestrator
gate *VectorGate // optional enforced re embed gate; nil means open (no vector store to verify)
}
// NewHandler builds the handler. orch may be nil (the brain is then reported unavailable rather than
// panicking, the graceful posture when the inference plane is unconfigured).
func NewHandler(orch *Orchestrator) *Handler { return &Handler{orch: orch} }
// WithVectorGate attaches the enforced re embed gate. When set, /ai/query refuses to serve until the
// gate confirms the stored vectors were produced by the configured embed model, so a half re embedded or
// wrong model store cannot silently mis rank a retrieval.
func (h *Handler) WithVectorGate(g *VectorGate) *Handler { h.gate = g; return h }
// Routes mounts the brain endpoint gated on the brain.query capability (the same tier as search.run).
func (h *Handler) Routes(r chi.Router) {
r.With(security.RequireCapability(security.CapBrainQuery)).Post("/ai/query", h.query)
}
// QueryRequest is the brain query. A free form question routes across CAG, RAG, and a refusal; an explicit
// intent runs the curated, parameterized, read only TAG path over the database of record. The brain never
// authors free form SQL.
type QueryRequest struct {
Question string `json:"question"`
Intent string `json:"intent,omitempty"`
Args []any `json:"args,omitempty"`
}
// AnswerResponse is the brain's answer plus its provenance: which augmented path served it, whether the
// prose was confirmed faithful or fell back to the deterministic rendering, and the evidence it was
// grounded on. A faithfulness fallback is a normal 200 with rationale_source deterministic.
type AnswerResponse struct {
Answer string `json:"answer"`
Path string `json:"path"` // cag | tag | rag | graph
RationaleSource string `json:"rationale_source"`
Citations []string `json:"citations"`
Faithful bool `json:"faithful"`
EvidenceCount int `json:"evidence_count"`
FallbackReason string `json:"fallback_reason,omitempty"`
}
func (h *Handler) query(w http.ResponseWriter, r *http.Request) {
if h.orch == nil {
writeErr(w, http.StatusServiceUnavailable, "brain_unavailable", "the brain is not configured")
return
}
// The enforced re embed gate: refuse to serve a query over a vector store that does not match the
// configured embed model, before any work, so a wrong model store never silently mis ranks.
if h.gate != nil {
if err := h.gate.Check(r.Context()); err != nil {
mapErr(w, err)
return
}
}
var in QueryRequest
dec := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20))
dec.DisallowUnknownFields()
if err := dec.Decode(&in); err != nil {
writeErr(w, http.StatusBadRequest, "bad_request", "invalid request body")
return
}
if strings.TrimSpace(in.Question) == "" && strings.TrimSpace(in.Intent) == "" {
writeErr(w, http.StatusUnprocessableEntity, "validation", "a question or an intent is required")
return
}
resp, err := h.orch.Answer(r.Context(), in.Question, in.Intent, in.Args)
if err != nil {
mapErr(w, err)
return
}
if resp.Refused {
// An unanswerable, unsafe, or out of allowlist question is a typed 4xx, never a raw error.
writeErr(w, http.StatusUnprocessableEntity, "refused", resp.RefusalReason)
return
}
writeJSON(w, http.StatusOK, AnswerResponse{
Answer: resp.Answer,
Path: pathOf(resp.Route),
RationaleSource: resp.RationaleSource,
Citations: resp.Citations,
Faithful: resp.Faithful,
EvidenceCount: resp.EvidenceCount,
FallbackReason: resp.FallbackReason,
})
}
// pathOf maps the internal route to the augmented path name the caller sees.
func pathOf(route Route) string {
switch route {
case RouteTAG:
return "tag"
case RouteRAG:
return "rag"
case RouteGraph:
return "graph"
default:
return "cag"
}
}
// mapErr maps a brain error to the typed envelope. It NEVER returns a raw SQL or database error string.
func mapErr(w http.ResponseWriter, err error) {
switch {
case errors.Is(err, ErrNoModel):
writeErr(w, http.StatusServiceUnavailable, "brain_unavailable", "the inference plane is not configured")
case errors.Is(err, ErrVectorModelMismatch):
writeErr(w, http.StatusServiceUnavailable, "vector_model_mismatch", "the stored vectors do not match the configured embed model; the brain is disabled until the store is re embedded")
case errors.Is(err, inference.ErrCircuitOpen), errors.Is(err, inference.ErrPlaneUnavailable):
writeErr(w, http.StatusServiceUnavailable, "plane_unavailable", "the inference plane is temporarily unavailable")
case errors.Is(err, ErrUnknownIntent):
writeErr(w, http.StatusUnprocessableEntity, "unknown_intent", "no such query intent")
case errors.Is(err, ErrEEOForbidden):
writeErr(w, http.StatusUnprocessableEntity, "refused", "the question references prohibited demographic data")
case errors.Is(err, tenant.ErrNoTenant):
writeErr(w, http.StatusForbidden, "no_tenant", "no validated tenant")
default:
// Any other failure (a bad intent argument, a database error) is reported generically so no raw SQL
// or internal detail leaks to the caller.
writeErr(w, http.StatusBadRequest, "brain_error", "the brain could not answer the question")
}
}
type errorBody struct {
Error errorEnvelope `json:"error"`
}
type errorEnvelope struct {
Code string `json:"code"`
Message string `json:"message"`
}
func writeErr(w http.ResponseWriter, status int, code, msg string) {
writeJSON(w, status, errorBody{errorEnvelope{Code: code, Message: msg}})
}
func writeJSON(w http.ResponseWriter, status int, v any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(v)
}