Documentation
¶
Overview ¶
Package search is one ranked result set over everything your org has stored.
It answers "what is RELEVANT" over a tenant's own data: it owns no store and fuses the two retrieval stores the platform already runs — the lexical index (apps/index) and the vector index (apps/knowledge) — into one ranked result set.
IT IS NOT MOUNTED. There is no manifest row and no plugin/search binary, so Mount below is never called and its POST /v1/search never reaches the wire — /v1/search belongs to apps/provisioning (list/create a provisioned search index), which is a different product that happens to share the word. The one live caller is apps/team's fulltext RPC, which calls ForOrg in-process; and in the team BINARY neither leg is mounted, so index.Ready() is false there and the lexical leg reports `disabled` on every query. Until a door is decided, a caller still has to know which of /v1/kb/search, /v1/index/indexes/:uid/search and /v1/code/search holds the answer, and gets a different request shape and a different score scale from each — which is the problem this package was written to end.
WHAT BELONGS HERE. A query whose honest answer has a SCORE. A query whose honest answer has a TRUTH VALUE — the definition of a symbol, the callers of a function, a dependency edge — belongs to /v1/code (apps/code) and must not be forced through a relevance-ranked shape: a definition is not 0.87 relevant, it either is the definition or it is not.
NOT HERE: /v1/websearch/search. That searches the PUBLIC WEB, not the customer's data. It has a different tenancy model (no org-scoped corpus), a different cost model (per-call to an external provider) and a different failure mode. It stays separate — do not fold it in.
DEGRADATION IS THE CONTRACT. Every response names every backend it consulted and that backend's status. A leg that is down produces results from the surviving legs plus an explicit `degraded` entry carrying the error — never a silent empty. This is not a nicety: a silent empty is exactly how a vector-store credential drift went unnoticed for five days behind a fail-empty /v1/kb/search.
Index ¶
Constants ¶
const ( BackendIndex = "index" // lexical, clients/index BackendVector = "vector" // semantic, clients/knowledge → hanzoai/vector )
Backend names. One constant per leg so the wire value is declared once and the provenance a client reads always matches the status it reads.
const ( StatusOK = "ok" StatusDegraded = "degraded" StatusDisabled = "disabled" StatusSkipped = "skipped" )
Backend statuses — four DISTINCT operational facts, never collapsed:
- ok the leg ran and answered.
- degraded the leg is configured but FAILED. Carries the error.
- disabled the leg is not provisioned in this deployment. Not a fault.
- skipped the caller's mode excluded the leg. Not a fault.
const ( ModeAuto = "auto" ModeText = "text" ModeSemantic = "semantic" ModeHybrid = "hybrid" )
Search modes. `auto` is the default and resolves to hybrid when both legs are available, else to whichever leg is.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type BackendStatus ¶
type BackendStatus struct {
Name string `json:"name"`
Status string `json:"status"`
Hits int `json:"hits"`
TookMS int64 `json:"took_ms"`
Error string `json:"error,omitempty"`
}
BackendStatus reports one leg's outcome. It is present for EVERY leg on EVERY response, including the ones that were skipped, so a client never has to infer from absence.
type Match ¶
type Match struct {
Backend string `json:"backend"`
Rank int `json:"rank"`
Score float64 `json:"score"`
}
Match is PROVENANCE: one backend's contribution to one result. A fused ranking without this is undebuggable — you cannot distinguish a hit both legs agreed on from a hit only one leg saw, nor tell a healthy leg from one quietly returning nothing.
type Request ¶
type Request struct {
// Query is the natural-language or keyword query. Required.
Query string `json:"query"`
// Mode selects the legs: auto (default) | text | semantic | hybrid.
Mode string `json:"mode,omitempty"`
// Project narrows to one project scope within the org.
Project string `json:"project,omitempty"`
// DocTypes restricts the semantic leg to a subset of indexed knowledge types.
DocTypes []string `json:"doctypes,omitempty"`
// Index names the lexical index to query. Defaults to "kb".
Index string `json:"index,omitempty"`
// Limit bounds the FUSED result set (default 10, max 50).
Limit int `json:"limit,omitempty"`
// Offset pages the fused result set.
Offset int `json:"offset,omitempty"`
}
Request is the ONE query shape. There is deliberately no `org` field: the tenant is the validated principal, so a caller can never search another org by asking.
type Response ¶
type Response struct {
// Status is the query's overall honesty signal:
// ok every consulted leg answered.
// partial at least one leg failed; Hits holds the survivors' results.
// unavailable every consulted leg failed; Hits is empty AND that is stated.
Status string `json:"status"`
// Mode is the mode actually used after `auto` resolution.
Mode string `json:"mode"`
// Hits is the fused, ranked result set.
Hits []Result `json:"hits"`
// Backends is the per-leg report. Always populated.
Backends []BackendStatus `json:"backends"`
TookMS int64 `json:"took_ms"`
}
Response is the ONE result shape.
func ForOrg ¶
ForOrg is the composition itself, for callers that have ALREADY established the tenant by some other means than an HTTP principal — notably the Team transactor, which runs in this same binary and holds a session whose workspace is its org. Such a caller gets the identical fused answer with no HTTP hop and no second retrieval path.
org MUST be a tenant the caller has authenticated. This function does not and cannot check that; it is the caller's boundary, exactly as it is for every other in-process store API in the codebase.
type Result ¶
type Result struct {
ID string `json:"id"`
Corpus string `json:"corpus"`
DocType string `json:"doctype,omitempty"`
Title string `json:"title,omitempty"`
URL string `json:"url,omitempty"`
Project string `json:"project,omitempty"`
Score float64 `json:"score"`
Matched []Match `json:"matched"`
}
Result is one fused hit. Score is the FUSED score (see fuse.go); each backend's native score stays in Matched, because the two are different things and flattening them loses the ability to explain a ranking.