engine.search(query, options?) // SearchResult[]
engine.searchAsync(query, options?) // Promise<SearchResult[]>
engine.searchDetailed(query, options?) // { results, related, meta }
engine.searchDetailedAsync(...)Invariant: search(q, opts) is semantically identical to searchDetailed(q, opts).results. Ranking, relationship, representative, and public result semantics are the same. Eligible ordinary search() / searchAsync() may execute via packed ranking-evidence views; searchDetailed() uses the diagnostic FeatureVector path and may additionally compute a full exact diagnostic plan for candidate titles, cycles, conflicts, and explanation successors.
searchAsync / searchDetailedAsync are the same algorithm with yields for cancellation. They must not change ordering.
| option | default | notes |
|---|---|---|
limit |
10 | Length of results |
relatedLimit |
5 | Length of related |
explain |
false | Attach JSON-serializable explanation |
relationshipStrategy |
engine default | hybrid | mixed | separate | none |
signal |
— | AbortSignal; abort throws AbortError, never [] |
candidateLimit |
engine default | Compatibility/experimental-retriever budget; exact indexed retrieval does not truncate to it |
resultCollector |
omitted | Optional built-in collector. "complete-interpretation" keeps documents that fully match an executed PhraseQuery or PhrasePrefixQuery, plus already-featured independent authored title evidence (short title-token prefix, configured title identity, exact title, contextual title prefix) and independently valid configured-prefix-recall candidates. When the typed phrase has no exact PhraseQuery hit, every PhrasePrefix field participates. When an exact hit exists, title/summary PhrasePrefix alternatives remain and body-only prefix expansions stay out. Occupancy, configured-content identity (a unique complete configured concept wrapped only by WH/copula/determiner tokens), and version queries skip it. Default search does not collapse. Enablement is the caller's (for example a product site), not Core policy. |
Unknown relationshipStrategy values throw InvalidConfigurationError.
resultCollector is a named built-in, not a plugin interface. It reads executed clause hits on the query plan. It does not re-parse the query, scan the corpus, or encode product activation rules.
{
id, title, rank,
score, // UNSTABLE: within-constraint number, not a calibrated score
relevanceKind, // "direct" | "related"
directClass, // "strong" | "moderate" | "weak" | "none"
relationship?, // present on related hits
retrievalSources?, // when explain: true
explanation?, // when explain: true
}Do not treat score as globally meaningful. Constraints dominate. meta on searchDetailed is experimental (timings, candidate counts, posting/block work, full feature evaluations, bound rejections, and pruning fallback reason). Counter names are not a stable public optimization API.
A cancelled search throws (DOMException named AbortError when available). Index state is unchanged. lastSearchMeta is only written on success.
Synchronous search() only observes signal.aborted at checkpoints. Use searchAsync or the Worker when abort must interrupt in-flight work.
Pre-aborted signals throw immediately.
Same documents, configuration, artifacts, and query produce the same ordering, explanations, and related rail. Ties break on document id. Cross-engine floating-point bit identity is not claimed.
Stable field roles are title, body, and optional summary. Role "summary" is short authored summary or search-description text, distinct from body. It is optional: omit the role to keep the two-field contract. Summary is phrase/configured/ranking evidence, not a third unigram posting field. See docs/schema.md.
SearchEngine.create({ plugins }) accepts SearchPlugin[]. compileAuthoredRelevance() returns the ordered SearchPlugin[] needed for configured-concept recognition and authored relevance. morphology() returns EnglishPlugin with suffix heuristics, repeat collapse, and no built-in lemma entries. Pass morphology({ lemmas }) to use a caller-owned table; those entries are the whole explicit table and take precedence over heuristics. canonicalLemma() does not promote a heuristic stem. Unmapped words still use the suffix heuristics. Callers that relied on the removed built-in families pass those mappings themselves. The table does not inherit object properties, so a token such as constructor is a string lookup rather than Object. There is no public dictionary() factory. Custom retrievers type as ExperimentalRetriever. Permissive duck-typed plugin objects remain valid at runtime.
Type contracts SearchPlugin, EnglishPlugin, and LexiconPlugin describe the duck-typed hooks Core actually reads (lemma, canonicalLemma, lexicon). They do not make analysis or ranking internals public, and they do not change runtime dispatch. Custom plugins may supply those morphology/lexicon hooks plus name / indexIdentity. lemma supplies morphological equivalence. A lemma that is not also the canonical normalized form is exact-only for ordinary lexical prefix matching. canonicalLemma identifies authoritative normalization; when supplied, that value becomes the query's normalized form and follows ordinary normalized prefix semantics. Configured concepts are not a custom-plugin contract; author configuredConcepts and compile with compileAuthoredRelevance(). Compiled related-recall tables are not public SearchPlugin fields; author relationshipMap kind: "related" and compile with compileAuthoredRelevance(). english() is not a public root export. Equivalent relevance is not a custom-plugin contract; author relationshipMap kind: "equivalent" and compile with compileAuthoredRelevance().
Configured concepts are authored as { key, aliases } plus optional identity metadata (type, provenance, confidence). Aliases are unordered semantic peers: alias array order has no search semantic effect. key remains stable concept identity and the lexical key form used for exact-key recognition/disambiguation. Former fields expansion / exp, primary, standaloneRecall, and topicalRecall are rejected on configuredConcepts rows. Those metadata fields are not ranking weights. migrateConfiguredEntry() returns a migration result containing entry: ConfiguredConcept ({ key, aliases, type?, provenance?, confidence? }); former exp / expansion becomes one peer alias; primary is discarded, and standalone/topical recall are extracted into relationship descriptors.
Once a query unambiguously occupies one configured concept, every authored spelling of that concept is the same search intent:
- the key and every peer alias produce identical ranked results (same candidate set, IDs, order, scores,
relevanceKind,directClass, and related rail) - typed surface stays on the query for explain/provenance and must not leak into ranking
- exact configured-key identity outranks another concept's one-token alias of that same typed form; two distinct exact keys still fail closed
- one-token aliases that are members of a longer peer form occupy only on exact typed identity
- multi-token configured forms may still complete an incomplete final token when preceding tokens uniquely identify the concept (
continuous d→cd). Ambiguous peer completions fail closed. - incomplete guessing of a configured key remains subject to the short-prefix information bound (
form.length < 3) - occupancy is whether one concept was uniquely established. Query analysis may still expose a non-occupied n≥2 configured prefix (
configuredPrefixSpans) for recognition/explain. A one-token proper prefix of a first form token isconfiguredPrefixRecall, not an occupancy span (nationais weaker NIST recall than exactnational). CandidateconfiguredFormCoverageis occupied matched-form completeness only: it is 0 unless the query uniquely occupies, and unique occupancy does not force coverage to 1. On a stop-tolerant left prefix, a skipped structural token is not a completed form token. - unoccupied unique configured-form prefixes attach
configuredPrefixRecallinstead of occupancy. Ordinary lexical retrieval stays active. Weak recall admits the concept key only (provenanceconfigured-prefix-recall).configuredPrefixRecallScorestores coverage for a prefix-only candidate whosedirectClassisnone, and also for an eligible exact-first title key. Ranking multiplies that coverage by the body-lexical coefficient (0.25). There is no separate bonus when the exact first token is shared by several concepts. Independent lexical evidence of a different token can contribute alongside that coverage. Coverage is omitted when configured title identity or the title-coverage matcher already accounts for the key, including an authored equivalent and a title lemma. Ambiguous exact first tokens do not occupy; they keep a title-key prefix-recall group and do not expand equivalent or topical forms. An exact-first title key does not becomekey-in-title. Adding another concept that shares the first token does not raise an existing document. Partial title-key evidence is not guaranteed to outrank a body hit of the typed word. Proper prefixes that match several concepts keep no unique recall but still retrieve every matching key. Those proper-prefix title keys still receive configured title identity (key-in-title); this is not occupancy. Leading wrapper stops are skipped for prefix recall. Ambiguous proper prefixes suppress body-prefix spray: the complete-interpretation collector keeps the matching configured keys instead of spraying body PhrasePrefix hits. Exact shared first tokens (open,hypertext,real,software) do not occupy and retain ordinary lexical hits of the typed word; they do not receivekey-in-title. There is no hard 1/N coverage cutoff. Packed ranking evidence fails closed for this query class (configured-prefix-recall) and uses the FeatureVector path. Independently retrieved configured-prefix-recall staysdirectwhen a relationship is later attached; equivalent/standalone/topical none-class neighbors with a relationship remainrelated. - configured-content identity is a separate query fact: one unique exact configured span occupying a query suffix, with only WH/copula/determiner stops before it. Occupancy is not broadened. Coordinators and prepositions (
and,vs,of, …) outside the span are unfinished composition, not wrappers. Prefix spans never qualify. - ranking may evaluate a uniquely identified configured concept's authored peer-form title when occupancy or configured-content identity is present. Identity unions that peer-form title equality with literal original-surface title equality; it does not set occupancy, bypass leading typed-stop occupancy blocks, mint configured aliases as typed phrases, or copy occupancy's looser peer-form prefix reduction. Structural-wrapper term concepts do not count as concept coverage under that ranking intent.
configuredConceptFieldEvidencerecords key vs complete form by field. Title is authored identity (configuredConceptMatch). Summary and body are weak mentions and do not become moderate/strong by themselves. Ranking does not consume summary vs body-only provenance. A single token of a multi-token form is not form evidence. Configured keys/forms do not mintexactTitleOrSummaryPhrase.- occupied ranking evaluates each peer form independently and reduces (usually max) order-independently. Peer aliases are not concatenated into one synthetic query. Stopwords may remain in authored forms for phrase structure, but they are not independent title/retrieval evidence. Identity ranking intent uses the same peer-form title/coverage reduction.
- explain
queryTokenCountfor an occupied or identity ranking-intent concept is the max non-stop length of one peer form. It is a debug summary, not a scoring denominator.
Public 0.5 explain names use peer-form terminology: configuredConceptMatch: "form", configuredSequenceIntent.matchedForm, configuredFormEvidence, configuredFormCoverage, configuredFormBodyMatch, QueryConcept.matchedForm / formCoverage, and contextualCompletion.source: "configured-form-prefix". 0.4 authoring still accepts exp / expansion through migrateConfiguredEntry(). Corpus-miner candidate expansion is unchanged.
import {
SearchEngine,
morphology,
compileAuthoredRelevance,
} from "@software-land/search";
const authored = compileAuthoredRelevance({
configuredConcepts: [{ key: "http", aliases: [["hypertext", "transfer", "protocol"]] }],
relationshipMap: {
hypertext: [{ to: { concept: "http" }, kind: "related" }],
qa: [{ to: { form: "testing" }, kind: "equivalent" }],
},
});
SearchEngine.create({
plugins: [morphology(), ...authored.plugins],
documentRelationships: authored.documentRelationships,
});authored.plugins is the canonical ordered plugin collection: configured-concept recognition (including related standalone/topical recall) then compiled equivalent one-hop recall. Applications author equivalent edges and do not separately construct a recall plugin.
CompiledAuthoredRelevance has two supported fields:
plugins— the correctly ordered authored relevance plugins forSearchEngine.create({ plugins: [morphology(), ...authored.plugins] })documentRelationships— compiled authored document→document edges as asearch-v2-relationshipsartifact, ornull
relationshipMap is directional. Kinds are equivalent and related. Endpoints are { form }, { concept }, or { document }. equivalent edges are directional and do not auto-reverse. qa → testing does not imply testing → qa. When equivalence is symmetric, author both directions. Trusted corpus-mined accepted groups do this automatically by compiling each group into a bidirectional equivalent clique. Edges must not carry numeric weights. Equivalent edges do not rewrite typed tokens.
| Authored edge | Compiles onto |
|---|---|
equivalent → form or concept |
existing one-hop recall machinery |
related 1-token → { concept } |
existing standalone-recall |
related concept → { form } |
existing topical-recall |
related document → { document } |
existing editorial relationship artifact (type: editorial, provenance manual, strength 1) |
Complete authored relevance — equivalent recall, related standalone/topical forms, and editorial document edges — is compileAuthoredRelevance({ configuredConcepts, relationshipMap, documents }). Pass ...authored.plugins and authored.documentRelationships to SearchEngine.create. Browser SearchClient.init({ configuredConcepts, relationshipMap, documentRelationships }) uses that same full authored-relevance meaning. Generated MiniLM relationships stay in the separate semantic artifact; merge them with authored editorial edges via mergeRelationships(semantic, authored.documentRelationships).
Directional equivalent recall is authored as relationshipMap equivalent edges and compiled by compileAuthoredRelevance(). Trusted corpus-mined groups compile to a bidirectional equivalent clique on relationshipMap.
migrateConfiguredEntry(old) is a one-shot conversion from { key, exp|expansion, aliases, primary, type, provenance, confidence, standaloneRecall, topicalRecall } into { entry, discardedPrimary, standaloneRelationships, topicalRelationships }, where entry is the migrated ConfiguredConcept ({ key, aliases, type?, provenance?, confidence? }). Runtime compileAuthoredRelevance() / SearchEngine do not call it. Identity metadata is preserved when supplied. primary is discarded and is not mapped to any relationship.
Explain output may still name compiled standaloneRecall / topicalRecall provenance. Equivalent recall is named equivalent-recall / equivalentRecall. Those are runtime/explain names, not authoring fields.