fusion

package
v1.0.0-beta.146 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 10, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package fusion is the generic deterministic-fusion engine: fan-out sub-query dispatch, dedup, rank, and budget enforcement over a GraphQueryClient, plus the product-supplied Lens SPI (lens.go). It is a near-leaf package — its only domain dependencies are the message package (for Triple in Entity and StorageReference as the Hydrate handle) and the leaf storage interface package (the Hydrate backend-deref contract); no NATS, no research-domain types (Intent, ExecutionOutput, RouteAction). Domain-coupled callers (processor/research-graph-execute) compose this engine and wrap the result in their own payload types; products supply a Lens.

Index

Constants

View Source
const (
	// DefaultMaxParallelism caps concurrent sub-query execution.
	// Keeps fan-out from saturating the responding gateways under
	// a wide-decompose scenario; operators can raise for staging
	// environments with more capacity.
	DefaultMaxParallelism = 8

	// DefaultMaxResultsPerSubquery caps per-sub-query result count
	// before ranking + budget enforcement run. Prevents a single
	// runaway sub-query from dominating the evidence array.
	DefaultMaxResultsPerSubquery = 50

	// DefaultBudgetTokens is the fallback evidence-budget when the
	// caller doesn't supply one. Should never normally fire when
	// callers supply a domain default, but acts as a safety net.
	DefaultBudgetTokens = 4000

	// DefaultPerSubqueryTimeoutFraction is the fraction of the
	// overall context deadline each sub-query gets as its per-call
	// timeout. 0.5 leaves slack for fan-out overhead and ranking.
	DefaultPerSubqueryTimeoutFraction = 0.5

	// DefaultExecuteTimeout is the fallback per-query timeout when
	// the caller's context has no deadline. Used only to bound
	// per-sub-query execution when ctx has no deadline set.
	DefaultExecuteTimeout = 60 * time.Second
)

Default engine constants. Callers that pass zero-value FuseOptions fields fall through to these.

View Source
const (
	FacetRelations = Facet(WantRelations) // the per-node relations map (forward + reverse roles)
	FacetPaths     = Facet(WantPaths)     // the outgoing paths DFS
	FacetImpact    = Facet(WantImpact)    // the incoming impact BFS
)

The three edge-walk facets — values structurally pinned to their Want twins.

View Source
const ContractVersion = "1"

ContractVersion identifies the wire shape of Request/Response.

Variables

This section is empty.

Functions

This section is empty.

Types

type BM25Args

type BM25Args struct {
	Query string `json:"query"`
	Limit int    `json:"limit,omitempty"`
}

BM25Args carries the text query + result cap.

type BodyResolver

type BodyResolver struct {
	// contains filtered or unexported fields
}

BodyResolver dereferences a Lens.Hydrate handle to its verbatim bytes. It is the engine-side helper the assemble step uses to populate a node body from the handle a lens returned — backend-agnostic via the injected StoreResolver.

func NewBodyResolver

func NewBodyResolver(r StoreResolver) *BodyResolver

NewBodyResolver builds a BodyResolver over the given StoreResolver. A nil resolver is permitted (ResolveBody then errors on any non-nil handle) so a deployment with no verbatim-body stores still constructs cleanly.

func (*BodyResolver) ResolveBody

func (b *BodyResolver) ResolveBody(ctx context.Context, ref *message.StorageReference) ([]byte, error)

ResolveBody returns the verbatim body bytes for a Hydrate handle.

Key granularity (gh#376 coordination): the handle's Key addresses the EXACT verbatim body — one pre-sliced body blob per entity (keyed by entity ID or content hash) — so Get returns the body byte-for-byte with NO engine-side line math. The lens's Locator (path + line range) is for citation/display only; the body comes pre-sliced through the handle, not by trimming a whole file.

  • A nil ref means "no verbatim body" — returns (nil, nil), NOT an error. (Lens.Hydrate returns (nil, nil) for body-less entities; this preserves that signal end-to-end.)
  • A non-nil ref with an empty StorageInstance, or an instance with no registered store, is a wiring/producer fault — returns an error so the caller can degrade the node (the engine omits the body; hydration is best-effort and does not fail the fuse — see Lens.Hydrate).
  • The store's Get error is propagated wrapped.

type Budget

type Budget struct {
	MaxNodes int `json:"max_nodes,omitempty"`
	MaxBytes int `json:"max_bytes,omitempty"`
}

Budget bounds a response. Zero fields take engine defaults.

type Direction

type Direction int

Direction selects edge traversal direction.

const (
	Outgoing Direction = iota
	Incoming
)

Outgoing follows a subject's predicates to targets; Incoming follows the reverse (who points at this entity).

type Edge

type Edge struct {
	Predicate string
	Target    string
}

Edge is a relationship from a subject entity to a target entity.

type EdgeSpec

type EdgeSpec struct {
	Predicate    string
	OutgoingRole string
	IncomingRole string  // "" to skip the reverse direction
	Facets       []Facet // nil/empty = all facets; else only the named facets
}

EdgeSpec declares one relationship predicate the engine should expand around a seed, with the role labels for its forward and (optional) reverse directions. For code: {Predicate: "code.relationship.calls", OutgoingRole: "callee", IncomingRole: "caller"}. An empty IncomingRole skips the reverse direction.

Facets optionally restricts which edge-walk facets this predicate feeds. An empty Facets means the edge participates in ALL three facets (relations, paths, impact) — the backward-compatible default. A non-empty Facets restricts the edge to exactly the named facets, so e.g. a containment edge can populate the relations map (Facets: {FacetRelations}) without inflating the impact walk.

type Engine

type Engine struct {
	// contains filtered or unexported fields
}

Engine runs the lens-driven deterministic fusion pipeline over a RetrievalClient, hydrating verbatim bodies through a BodyResolver.

func NewEngine

func NewEngine(graph RetrievalClient, body *BodyResolver) *Engine

NewEngine builds a lens-driven engine. body may be nil — node bodies are then omitted (the response degrades gracefully rather than panicking).

func (*Engine) Fuse

func (e *Engine) Fuse(ctx context.Context, req Request, lens Lens) (Response, error)

Fuse resolves req against the graph through lens and returns the fused response. Readiness is load-bearing: a not-ready graph yields an empty envelope (the caller must fall back); ready+absent yields a miss with near-matches — never an ambiguous empty. A backend failure fetching seeds is surfaced as an error, NOT silently turned into a "not found" (that would violate the ready≠not-found contract).

func (*Engine) WithSignals

func (e *Engine) WithSignals(s RankSignals) *Engine

WithSignals attaches the framework ranking signals — ontology specificity + predicate salience (ADR-062 increment 5, gh#396) — folded into ranking on top of resolve-order + lexical. Returns the engine for chaining. Passing nil (the default) keeps ranking at resolve-order + lexical.

type Entity

type Entity struct {
	ID      string
	Triples []message.Triple
}

Entity is the minimal projection a Lens reads: the entity's ID and its current triples. Kept deliberately small (no graph.EntityState, no NATS) so pkg/fusion stays a near-leaf — the Lens reads human-facing fields off the triples.

func (*Entity) First

func (e *Entity) First(predicate string) string

First returns the first object for predicate rendered as a string, or "". A convenience for lenses/ranker so they read triple objects without touching the triple shape.

func (*Entity) FirstInt

func (e *Entity) FirstInt(predicate string) int

FirstInt returns the first object for predicate as an int, or 0.

type EntityStateArgs

type EntityStateArgs struct {
	EntityIDs []string `json:"entity_ids"`
}

EntityStateArgs carries IDs to fetch.

type Evidence

type Evidence struct {
	// EntityID is the graph entity this evidence refers to. Required.
	EntityID string `json:"entity_id"`

	// Tier is the retrieval tier that surfaced this hit: "0" (rules /
	// predicate queries), "1" (BM25), or "2" (neural — deferred to
	// Phase 2). Operators may use it to filter evidence by retrieval
	// method.
	Tier string `json:"tier"`

	// Source is the retrieval source within the tier — e.g.
	// "classifier", "predicate_walk", "bm25_index". Required.
	Source string `json:"source"`

	// Score is the within-tier ranking score (higher = better).
	// Cross-tier comparison is not meaningful in Phase 1 (per-tier
	// ordering + recency tie-break); Phase 2 may add a learned ranker.
	Score float64 `json:"score,omitempty"`

	// SnippetText is a short inline preview (prompt-injection
	// friendly). Omit when the evidence has no readable preview.
	SnippetText string `json:"snippet_text,omitempty"`

	// ObjectStoreRef is the ObjectStore key for the full body when
	// the evidence has bulk content. Empty when the evidence is fully
	// expressed in the EntityID + triples on the graph.
	ObjectStoreRef string `json:"objectstore_ref,omitempty"`
}

Evidence is one item in a fused evidence set — a single entity hit or evidence snippet the retrieval fan-out surfaced. Provenance is mandatory: every evidence item carries enough metadata (EntityID + Tier + Source) for the caller to verify it back against the graph and for downstream consumers (e.g. synthesize_answer's quote-back validation) to reject fabricated refs.

ObjectStoreRef is set when the body of the evidence (long text, document, etc.) lives in ObjectStore; consumers read it via that ref. SnippetText carries a short inline preview for prompt-injection readability without triggering ObjectStore round-trips on every hit.

func (*Evidence) Validate

func (e *Evidence) Validate() error

Validate checks the required fields and rejects unknown tier values.

type Facet

type Facet string

Facet names one of the three edge-walk facets the engine derives from a lens's EdgeSpecs. It is a distinct type from Want (so WantBody, which is not edge-driven, is unpassable), but its values are DERIVED from the matching Want constants below so the two vocabularies cannot drift. (WantBody has no Facet.)

type FuseOptions

type FuseOptions struct {
	// MaxParallelism caps concurrent sub-query execution. 0 uses DefaultMaxParallelism.
	MaxParallelism int

	// MaxResultsPerSubquery caps result count per sub-query before
	// ranking + budget enforcement run. 0 uses DefaultMaxResultsPerSubquery.
	MaxResultsPerSubquery int

	// BudgetTokens is the estimated-token budget for the returned
	// evidence set. 0 uses DefaultBudgetTokens.
	BudgetTokens int
}

FuseOptions controls the engine's concurrency, per-subquery result cap, and evidence budget. Zero values fall through to the Default* constants.

type FuseResult

type FuseResult struct {
	// Evidence is the kept evidence set after dedup, sort, and budget
	// enforcement. May be empty (degraded path or zero queries).
	Evidence []Evidence

	// Degraded flags incomplete fan-out — e.g. one or more sub-query
	// errors or a timeout. The caller should surface this to operators
	// or downstream LLM consumers as low-confidence input.
	Degraded bool

	// DegradedReason explains why Degraded is set. Multiple per-sub-query
	// failures are joined with "; ".
	DegradedReason string

	// BudgetTokensUsed is the estimated token cost of the kept evidence
	// array after budget enforcement. Useful for operator observability
	// and tuning budget parameters.
	BudgetTokensUsed int
}

FuseResult is the engine's output: dedup'd + ranked + budget-enforced evidence, plus observability fields.

func Fuse

func Fuse(ctx context.Context, gq GraphQueryClient, queries []SubQuery, opts FuseOptions, logger *slog.Logger) (FuseResult, error)

Fuse runs the materialized sub-query set in parallel, deduplicates by EntityID (across tiers), sorts by tier + score + entity-ID tie-break, enforces the token budget, and returns a FuseResult.

gq must not be nil. An empty queries slice is not an error — it returns FuseResult{Degraded: true, DegradedReason: "..."}.

Per-sub-query failures are degrading, NOT chain-failing: they set FuseResult.Degraded and append to DegradedReason without cancelling sibling sub-queries. The only hard-fail is a parent-context cancellation, which returns a non-nil error.

The logger parameter is optional; nil uses slog.Default().

type GraphQueryClient

type GraphQueryClient interface {
	EntityState(ctx context.Context, args EntityStateArgs, tier, source string, limit int) ([]Evidence, error)
	PredicateWalk(ctx context.Context, args PredicateWalkArgs, tier, source string, limit int) ([]Evidence, error)
	TemporalRange(ctx context.Context, args TemporalRangeArgs, tier, source string, limit int) ([]Evidence, error)
	BM25(ctx context.Context, args BM25Args, tier, source string, limit int) ([]Evidence, error)
}

GraphQueryClient is the narrow retrieval surface the fusion engine consumes. Production implementations wrap NATS-direct subjects; tests substitute in-memory fakes so the test matrix doesn't need a live graph stack.

Per-method semantics:

  • EntityState: returns the current Triples for the named entities, projected into Evidence (one per entity).
  • PredicateWalk: from each seed, returns Evidence for entities reachable within MaxHops via Predicates (empty Predicates = all).
  • TemporalRange: returns Evidence for entities within the time window, optionally filtered by topic.
  • BM25: text search via graph-query's existing BM25 surface.

All methods MUST stamp Tier + Source on every returned Evidence (taken from the SubQuery they were dispatched for) so provenance stays honest end-to-end. The engine does NOT re-stamp.

type Impact

type Impact struct {
	Nodes     int  `json:"nodes"`
	Files     int  `json:"files"`
	Truncated bool `json:"truncated"`
}

Impact summarizes the transitive reverse-relation closure of the response's seeds — the blast radius of changing them — carried on Response.Impact. It counts distinct affected nodes and files (a lens Location path); Truncated marks the node cap was hit, so the counts are a lower bound.

type IndexState

type IndexState string

IndexState mirrors the graph readiness phase.

const (
	StateBuilding IndexState = "building"
	StateReady    IndexState = "ready"
	StateDegraded IndexState = "degraded"
)

The readiness phases. Only Ready permits a not-found conclusion.

type IndexStatus

type IndexStatus struct {
	Ready bool       `json:"ready"`
	State IndexState `json:"state"`
	// IndexedRevision / TargetRevision / Lag expose the exact revision-lag so a
	// caller that knows its own target revision can gate on IndexedRevision >=
	// myRev rather than the coarse global Ready bool (ADR-066).
	IndexedRevision uint64 `json:"indexed_revision,omitempty"`
	TargetRevision  uint64 `json:"target_revision,omitempty"`
	Lag             uint64 `json:"lag,omitempty"`
	Phase           string `json:"phase,omitempty"`
	Revision        string `json:"revision,omitempty"`
	LastSynced      string `json:"last_synced,omitempty"`
}

IndexStatus is attached to every response. Ready is load-bearing: when false the caller must fall back (e.g. to grep) rather than treat empty as not-found. Ready now means the index is CAUGHT UP (revision-lag), not merely started (ADR-066). Field-identical to graph.IndexStatusResponse — the RetrievalClient decodes graph.index.query.status directly into this; the two change together.

type Lens

type Lens interface {
	// Name identifies the lens (e.g. "code", "docs"). Used as the registry key.
	Name() string

	// ResolveMode picks how to turn the raw query into seeds.
	ResolveMode(query string) ResolveMode

	// Edges are the relationship predicates to expand around each seed.
	Edges() []EdgeSpec

	// Label is the entity's human name (e.g. from dc.terms.title).
	Label(e *Entity) string

	// Kind is a short human kind for the entity (e.g. "function", "doc").
	Kind(e *Entity) string

	// Location returns the entity's domain-general place (file path or URL,
	// optional section fragment, optional line range).
	Location(e *Entity) Locator

	// Hydrate returns a HANDLE to the entity's verbatim body — never the bytes
	// themselves and never a filesystem read. The engine resolves the handle's
	// StorageInstance to a registered storage.Store and reads the body with
	// Get(Key) (ADR-062 hydration contract, increment 4). Returning a handle
	// (not a string) is what makes fusion deployment-independent: a remote
	// caller of a standalone fusion service (semsource ADR-0006) cannot read the
	// service's worktree, so bodies must be addressable through a backend-
	// agnostic store. Return (nil, nil) when the entity has no verbatim body.
	//
	// Error policy (engine contract): hydration is best-effort. A non-nil error
	// DEGRADES the response — the engine omits that node's body and continues —
	// it does NOT fail the fuse. This mirrors the engine's degrade-don't-fail
	// posture for sub-query failures (see Fuse). Lenses should return an error
	// only for genuine retrieval faults, not for "no body" (use (nil, nil)).
	Hydrate(ctx context.Context, e *Entity) (*message.StorageReference, error)
}

Lens supplies the only domain-specific parts of fusion (ADR-062). The engine owns resolve / expand / rank / budget / envelope; the lens declares which edges to walk, how to read an entity's human-facing fields, and how to hydrate its verbatim body.

type Locator

type Locator struct {
	Path     string // file path or URL
	Fragment string // section / anchor (docs)
	Lines    [2]int // line range (code); zero value means line-less
}

Locator is a domain-general place: a file path or URL, an optional section / anchor fragment (docs), and an optional line range (code). One of Lines or Fragment is typically set, not both.

type MapStoreResolver

type MapStoreResolver map[string]storage.Store

MapStoreResolver is a static StoreResolver over a name→Store map. The zero value (nil map) resolves nothing. Safe for concurrent reads (Go map reads are safe without writers; the map is build-once at wiring time).

func (MapStoreResolver) Store

func (m MapStoreResolver) Store(instance string) (storage.Store, bool)

Store implements StoreResolver.

type Miss

type Miss struct {
	Query      string   `json:"query"`
	DidYouMean []string `json:"did_you_mean,omitempty"`
}

Miss reports a query that resolved to nothing while the graph was ready, with near-matches. A Miss only appears when Ready is true.

type Node

type Node struct {
	Name     string `json:"name"`
	Kind     string `json:"kind,omitempty"`
	Path     string `json:"path,omitempty"`
	Fragment string `json:"fragment,omitempty"`
	// Lines is [start,end] for code, nil for line-less domains (docs) — a slice
	// so omitempty actually omits it rather than emitting a spurious [0,0].
	Lines     []int            `json:"lines,omitempty"`
	Body      string           `json:"body,omitempty"`
	Relations map[string][]Ref `json:"relations,omitempty"`
	// Class is the BFO/CCO class IRI (provenance/debug; the agent ignores it).
	Class string `json:"class,omitempty"`
	// Handle is an opaque continuation token (internally the entity ID). Not an
	// addressing scheme: never parse or construct it.
	Handle string `json:"handle,omitempty"`
}

Node is one fused result: verbatim body plus the structure around it. Domains differ only in which roles populate Relations (code: callers/callees; docs: links/sections).

type Path

type Path struct {
	Names     []string `json:"names"`
	Truncated bool     `json:"truncated,omitempty"`
}

Path is a bounded outgoing relation walk from a seed, rendered as a sequence of human names (never entity IDs), carried on Response.Paths. Truncated marks a path cut short at the depth cap or a cycle — the partial path is kept, not dropped.

type PredicateWalkArgs

type PredicateWalkArgs struct {
	Seeds      []string `json:"seeds"`
	Predicates []string `json:"predicates,omitempty"`
	MaxHops    int      `json:"max_hops,omitempty"`
}

PredicateWalkArgs carries seed IDs + optional predicate filter. MaxHops 0 resolves to 1 at the executor.

type Provenance

type Provenance string

Provenance records how an answer was produced so callers can calibrate trust.

const (
	ProvenanceDeterministic Provenance = "deterministic" // exact lookup + structural walk
	ProvenanceEmbedding     Provenance = "embedding"     // seeds came from semantic search
	ProvenanceLLM           Provenance = "llm"           // an LLM reasoned over the result
)

The provenance tiers, in increasing order of uncertainty.

func ProvenanceForMode

func ProvenanceForMode(mode ResolveMode) Provenance

ProvenanceForMode maps a ResolveMode to the honesty-envelope provenance tier: symbol/prefix seeds are deterministic exact lookups; nl seeds come from embedding search. (The research_graph LLM sibling is the only "llm" source — the deterministic engine never sets it.) Exported so the engine's assemble step and any lens-driven caller stamp provenance consistently.

Deliberate divergence from semsource's provenanceFor (source/fusion/engine.go), whose catch-all is "deterministic" and only embedding is named: there, ResolveMode is a CLOSED int enum so "unknown" is unreachable. Here ResolveMode is an OPEN string type, so the catch-all must be the conservative tier — "embedding" — because only "deterministic" permits a caller to treat an empty result as an authoritative not-found, and an unclassified seed has not earned that. CAUTION: this means any NEW deterministic mode added to the enum MUST be added to the deterministic case below, or it silently degrades to embedding (no compile error — open string enum).

type RankSignals

type RankSignals interface {
	// ClassSpecificity scores how specific an ontology class IRI is: more
	// specific (deeper in the BFO/CCO subclass tree) = higher; 0 for an
	// unknown or unset class. A deeper class carries more information, so within
	// the same resolve/lexical tier a precisely-typed entity reorders ahead of a
	// vaguely-typed peer.
	ClassSpecificity(classIRI string) float64

	// PredicateSalience returns a predicate's stored salience weight, SIGNED:
	// positive boosts (an entity carrying the fact reorders ahead), negative
	// demotes (it reorders behind), 0 is neutral. Production reads vocabulary
	// PredicateMetadata.Weight. A negative weight is how a consumer down-ranks
	// structurally-identifiable noise (tests, generated code, mocks) that carries
	// the same boosted predicates as the real thing — additive boosting alone
	// cannot separate them (gh#441). The engine folds an entity's strongest boost
	// and strongest demotion together (see entitySalience), so a demotion is a
	// bounded secondary reordering, never an exclusion.
	PredicateSalience(predicate string) float64
}

RankSignals supplies the framework ranking signals the lens engine folds into rankEntities on top of resolve-order + lexical: ontology specificity and predicate salience (ADR-062 increment 5, gh#396). Injected through an interface so pkg/fusion stays a leaf (no vocabulary / bfo / cco imports); production wires the vocabulary registry + BFO/CCO subclass helper (see pkg/fusion/fusionvocab). A nil RankSignals leaves ranking at resolve-order + lexical — the increments 1–4 behavior — so attaching signals is purely additive to the pipeline (nil → unchanged). The salience signal itself is DIRECTIONAL: a predicate's weight is signed, so it can down-rank as well as boost (gh#441).

type Ref

type Ref struct {
	Name     string `json:"name"`
	Path     string `json:"path,omitempty"`
	Fragment string `json:"fragment,omitempty"`
	Line     int    `json:"line,omitempty"`
}

Ref points to a node by what a human reads — never the entity ID.

type Request

type Request struct {
	Query string `json:"query"`
	Want  []Want `json:"want,omitempty"`
	// Scope optionally constrains NL seed resolution to entities whose ID
	// matches at least one of these dot-delimited entity-ID prefixes
	// (OR-matched). Empty/absent means no filter — today's behavior. It lets a
	// lens instance over a shared embedding index retrieve only its own domain
	// so a smaller domain is not diluted by a larger co-resident one (ADR-071).
	// A list, not a scalar: one domain often spans several prefixes (semsource
	// "all code" = golang/python/ts/svelte). NL-only; ignored by symbol/prefix
	// resolve modes.
	Scope  []string `json:"scope,omitempty"`
	Budget Budget   `json:"budget,omitzero"`
}

Request is the fused query, keyed by what an agent already knows — never an entity ID.

type ResolveMode

type ResolveMode string

ResolveMode picks how the engine turns a raw query string into seed entities. The Lens classifies the query; the engine maps the mode to a resolve strategy (symbol → graph.query.byName exact lookup, prefix → graph.query.prefix, nl → semantic search). Provenance follows from the mode: symbol/prefix are deterministic, nl is embedding.

const (
	// ResolveModeNL routes the query to semantic (embedding) search. Seeds are
	// embedding-ranked, so the response provenance is "embedding", not
	// "deterministic".
	ResolveModeNL ResolveMode = "nl"

	// ResolveModeSymbol routes to deterministic exact name/title lookup
	// (graph.query.byName). The provenance the byName index makes honest.
	ResolveModeSymbol ResolveMode = "symbol"

	// ResolveModePrefix routes to deterministic prefix lookup
	// (graph.query.prefix) — e.g. a path or namespace stem.
	ResolveModePrefix ResolveMode = "prefix"
)

type ResolveQuery

type ResolveQuery struct {
	Query string
	Mode  ResolveMode
	Scope []string
	Limit int
}

ResolveQuery is the argument set for RetrievalClient.Resolve. Mode selects the resolve strategy; Scope is honored only for ResolveModeNL (a filter on embedding candidates), where empty/nil means no filter.

type Response

type Response struct {
	Index           IndexStatus `json:"index"`
	Provenance      Provenance  `json:"provenance"`
	Nodes           []Node      `json:"nodes,omitempty"`
	Paths           []Path      `json:"paths,omitempty"`
	Impact          *Impact     `json:"impact,omitempty"`
	Misses          []Miss      `json:"misses,omitempty"`
	Truncated       bool        `json:"truncated"`
	ContractVersion string      `json:"contract_version"`
}

Response is the fused answer. Nodes is the payload; Index and Provenance are the honesty envelope. Paths and Impact are optional facets, present only when the request Wants them (WantPaths / WantImpact).

type RetrievalClient

type RetrievalClient interface {
	// Status reports graph readiness. The honesty envelope's Ready flag is
	// load-bearing — only Ready permits a not-found conclusion.
	Status(ctx context.Context) (IndexStatus, error)
	// Resolve maps a query to seed entity IDs, most relevant first. Its
	// arguments are a struct rather than positional so the NL-only Scope does
	// not force symbol/prefix callers to pass an ignored value, and so a future
	// resolve dimension adds a field instead of re-breaking every impl and fake
	// (ADR-071).
	Resolve(ctx context.Context, q ResolveQuery) ([]string, error)
	// Entity returns an entity by ID, or (nil, nil) if absent.
	Entity(ctx context.Context, id string) (*Entity, error)
	// Entities batch-fetches entities by ID. Absent IDs are omitted; a non-nil
	// error means a BACKEND failure, which callers MUST distinguish from genuine
	// absence (an empty result on a Ready graph is a miss, not a fault).
	Entities(ctx context.Context, ids []string) ([]*Entity, error)
	// Neighbors returns edges from id along the given predicates in a direction.
	Neighbors(ctx context.Context, id string, predicates []string, dir Direction) ([]Edge, error)
	// Names suggests entity display names near a query (for a miss's did_you_mean).
	Names(ctx context.Context, query string, limit int) ([]string, error)
}

RetrievalClient is the resolve/expand surface the lens-driven Engine composes over (ADR-062 lens-driven entry). It is DISTINCT from GraphQueryClient (the sub-query executor surface the package-level Fuse consumes): this one maps a query to seeds and walks structure, whereas GraphQueryClient runs pre-built sub-queries. The ADR's eventual convergence unifies them; for now they serve the two engine entries side by side.

Production wraps NATS request/reply (graph.query.{status,byName,prefix, semantic,batch,relationships,entity}); tests use an in-memory fake. Keeping the engine behind this interface is what lets it stay deterministic and unit-testable without a live graph (the production impl + the readiness status subject are PR B / gh#397).

type StoreResolver

type StoreResolver interface {
	// Store returns the store registered under instance, and whether one exists.
	Store(instance string) (storage.Store, bool)
}

StoreResolver maps a StorageReference.StorageInstance name to the storage.Store that holds its data. Wiring supplies it — typically MapStoreResolver over the deployment's registered stores. Kept as an interface so the engine never assumes one backend and tests can substitute fakes.

Canonical key (gh#376 coordination with semsource): StorageInstance is the storage COMPONENT INSTANCE NAME (e.g. "objectstore", "filestore-media"), per StorageReference's own doc ("identifies which storage component holds the data … enables federation across multiple storage instances") — NOT the bucket name. Producers stamp the component instance name; wiring registers each store under that same name. (semstreams' own auto-stamp is inconsistent today — component.go uses the instance name, store.go uses the bucket — tracked separately; this helper, the first consumer, fixes the convention at instance-name.)

type SubQuery

type SubQuery struct {
	Type   SubQueryType `json:"type"`
	Tier   string       `json:"tier"`   // "0" (predicate) or "1" (BM25)
	Source string       `json:"source"` // e.g. "walk_seeds:drone-001"; surfaces on Evidence.Source

	// Per-type args. Exactly one is populated based on Type — the
	// executor switch enforces. Optional fields allow tests + future
	// templates to omit per-type primitives that don't apply.
	EntityState   *EntityStateArgs   `json:"entity_state,omitempty"`
	PredicateWalk *PredicateWalkArgs `json:"predicate_walk,omitempty"`
	TemporalRange *TemporalRangeArgs `json:"temporal_range,omitempty"`
	BM25          *BM25Args          `json:"bm25,omitempty"`
}

SubQuery is a typed retrieval request. Each variant carries the minimum fields its tier executor needs; the Type discriminator drives dispatch in the engine's executeSubQuery.

Tier and Source travel with the SubQuery so the per-tier executor can stamp them onto every Evidence it produces without re-deriving — keeps provenance honest end-to-end.

func (SubQuery) Validate

func (q SubQuery) Validate() error

Validate returns a non-nil error when required per-type fields are missing. Called by the materializer before fan-out so a malformed sub-query surfaces before any retrieval work runs.

type SubQueryType

type SubQueryType string

SubQueryType is the closed set of Tier 0+1 primitives. Phase 2 adds spatial_polygon + neural; Phase 1 stays minimal.

const (
	// SubQueryTypeEntityState fetches current state of named
	// entities. Tier 0; used for walk_seeds anchoring + decompose's
	// entity_type axis when classifier candidates are present.
	SubQueryTypeEntityState SubQueryType = "entity_state"

	// SubQueryTypePredicateWalk traverses predicate(s) from seed
	// entities. Tier 0; used for walk_seeds neighborhood expansion +
	// decompose's free-form axis fallback.
	SubQueryTypePredicateWalk SubQueryType = "predicate_walk"

	// SubQueryTypeTemporalRange queries entities within a time
	// window. Tier 0; used for decompose's time axis. Phase 1 ships
	// the type but the executor falls through to BM25 on topic when
	// graph-index-temporal isn't wired — operators see the
	// "temporal degrade" hint on the produced Evidence.
	SubQueryTypeTemporalRange SubQueryType = "temporal_range"

	// SubQueryTypeBM25 text-searches via graph-query's existing
	// BM25 surface. Tier 1; always added to widen coverage beyond
	// purely-structural retrieval (intent-shaped routing can miss
	// keyword matches the classifier surfaced).
	SubQueryTypeBM25 SubQueryType = "bm25"
)

type TemporalRangeArgs

type TemporalRangeArgs struct {
	// Start / End are RFC3339 strings; timezone handling stays in the
	// executor where the upstream graph-index-temporal surface lives.
	Start string `json:"start"`
	End   string `json:"end"`
	Topic string `json:"topic,omitempty"`
}

TemporalRangeArgs carries start/end + an optional topic filter. Empty Topic widens to all entities in the window (may be heavy; callers should always pass a topic in practice).

type Want

type Want string

Want enumerates the optional facets a caller can request. Empty defaults to body plus immediate relations.

const (
	WantBody      Want = "body"      // verbatim source/passage
	WantRelations Want = "relations" // callers/callees, links/sections
	WantPaths     Want = "paths"     // bounded outgoing relation paths from the seeds
	WantImpact    Want = "impact"    // transitive reverse-relation closure of the seeds
)

The requestable facets.

Directories

Path Synopsis
Package fusionnats is the production NATS implementation of fusion.RetrievalClient (ADR-062 increment "B2"): it wires the engine's resolve/expand/status surface onto the existing public graph query subjects so the lens-driven fusion engine can run against a live graph.
Package fusionnats is the production NATS implementation of fusion.RetrievalClient (ADR-062 increment "B2"): it wires the engine's resolve/expand/status surface onto the existing public graph query subjects so the lens-driven fusion engine can run against a live graph.
Package fusionvocab is the production implementation of fusion.RankSignals (ADR-062 increment 5, gh#396): it wires the lens engine's ranking signals onto the vocabulary registry (predicate salience) and the BFO/CCO subclass helper (ontology specificity).
Package fusionvocab is the production implementation of fusion.RankSignals (ADR-062 increment 5, gh#396): it wires the lens engine's ranking signals onto the vocabulary registry (predicate salience) and the BFO/CCO subclass helper (ontology specificity).
Package lensregistry holds the explicit, instance-scoped registry of fusion Lens factories (ADR-062 increment 3).
Package lensregistry holds the explicit, instance-scoped registry of fusion Lens factories (ADR-062 increment 3).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL