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 | 4b8e7ce594c068621867f9c9deed0ec64ae6c553 |
| Branch | ws3-clearance |
| Path or archive member | internal/modules/ai/handler.go |
| Original SHA-256 | 3941276675ec37b13e82ba96ec964d3d208fdb46c3c7d733cd8072bca1635a9d |
| Git blob SHA | a88e3db82c40bab0676ea682c8323435655e9a15 |
| Reading-copy SHA-256 | 3941276675ec37b13e82ba96ec964d3d208fdb46c3c7d733cd8072bca1635a9d |
Every recorded occurrence
These 21 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 | ws3-clearance | 4b8e7ce594c068621867f9c9deed0ec64ae6c553 | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | ws7-federal-signal | 6a9f6ce9cfb13345ffb47770d4960e7c2d9c17c2 | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | ws6-retrieval | 6c56e29231ec05735927fa265c97d517499dfeaf | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | per-tenant-routing | 9bde0f9a74b89d01980fa658e5253acda7528d0c | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | ai-brain-live | aac32fc49c878cc5bca099459307611562d0bac1 | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | ws1-ontology-sync | b596f449a32a1016fbd403c36505cc00a1ac9ee6 | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | deploy-and-ops | bdacd9d0b4d5998d25fc74bf4007f4c3ec538c7a | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | ws8-teaching-loop | c823f0fb6bb2aba0f39001eb6de96f4000d9321f | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | phase2-eval-fairness | d361af254701975fe239927656ababb56374655e | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | ws2-alias-relationship | e4dada4601c6eb101830f9d223a9655dfa120616 | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | ws9-bench-ingestion | f585a9ef32fa554663ce1fba9a414a54b6e49cf0 | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | per-tenant-routing | 9bde0f9a74b89d01980fa658e5253acda7528d0c | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | ws6-retrieval | 6c56e29231ec05735927fa265c97d517499dfeaf | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | ws1-ontology-sync | b596f449a32a1016fbd403c36505cc00a1ac9ee6 | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | ws8-teaching-loop | c823f0fb6bb2aba0f39001eb6de96f4000d9321f | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | ws3-clearance | 4b8e7ce594c068621867f9c9deed0ec64ae6c553 | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | phase2-eval-fairness | d361af254701975fe239927656ababb56374655e | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | deploy-and-ops | bdacd9d0b4d5998d25fc74bf4007f4c3ec538c7a | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | ws2-alias-relationship | e4dada4601c6eb101830f9d223a9655dfa120616 | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | ws7-federal-signal | 6a9f6ce9cfb13345ffb47770d4960e7c2d9c17c2 | Open original |
| Janady13/missionmatch / internal/modules/ai/handler.go | ai-brain-live | aac32fc49c878cc5bca099459307611562d0bac1 | Open original |
Recovered source transcript
Download complete source text ↓160 lines · 6.3 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
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"
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)
}