A correctness release from a full audit of the indexing, retrieval, CLI and
documentation surfaces. Most of it is defence: a refused deletion plan can no longer
be retried into a deletion, a truncated crawl can no longer authorize one, an
unreadable robots.txt no longer fails open, and clean can no longer delete the
project. Alongside that, chunk metadata that had been going stale forever — tags,
ordinals, route files, hook output — now reaches storage.
Three changes need action. The inert embedding.* config keys are now rejected
rather than ignored, the svelte peer floor moved to ^5.20.0, and pathPrefix
matches whole path segments. Each is marked Breaking: below.
Upgrade cost
The first indexing run after upgrading re-writes more than usual. Nothing is lost, and
the extra cost is one-off:
- Every page summary re-upserts once. The page fingerprint is now computed over
canonical JSON, so every existing page hash differs and each page summary is written
again. - Unchanged chunks get a metadata-only refresh — no re-embedding. The text is not
re-sent, so these cost a write but not an embedding. content-fileschunks whose text contained HTML entities re-embed once. Raw
.svelteextraction now decodes entities properly, so that text genuinely changed.
Changed
-
Breaking: the inert
embedding.*options are rejected instead of ignored.
embedding.model,embedding.dimensionsandembedding.taskTypewere validated,
merged and typed as required, and read by nothing — embeddings are generated by
Upstash from the text SearchSocket sends, so the model is a property of the index
and is chosen when that index is created. They joinembedding.batchSizeand
embedding.images.enablein the removed-key list, so a config that still sets one
now fails to load with a message naming the replacement. Delete the keys; to run a
different model, create a new Upstash index with it and pointupstash.url/
upstash.tokenat that index. -
A refused large-deletion plan now writes nothing at all. Previously a run stopped by
indexing.maxDeletionRatiostill upserted its additions, which enlarged the
inventory the ratio is measured against — an identical retry then saw the same stale
records as a smaller fraction and deleted them without--accept-large-deletion
being supplied. The whole mutation is now refused, so a refused plan stays refused
however often it is repeated. The separate empty-source guard is unchanged: it stops
deletions but still writes the run's additions. -
An unreadable
robots.txtblocks index writes incrawlmode, the only mode that
fetches it over the network. If the fetch times out, returns a server error, or exceeds
the 512 KiB size cap, the run cannot tell which pages the site means to exclude, so it
writes nothing rather than risk publishing an excluded page. A 404 or 410 still means
"no rules here" and indexing proceeds.static-output/buildreadrobots.txtfrom
the build directory and treat an unreadable file as absent. -
robots.txtmatching follows RFC 9309 for*wildcards, the$end-anchor, group
merging across consecutiveUser-agentlines, and longest-rule-wins specificity. Not
certified-complete: percent-encoded paths are compared as written, not canonicalised. -
llms.txt/llms-full.txtare only republished by a run holding the complete
page list. A truncated crawl, a failed extraction, or a refused deletion plan
leaves the existing files alone. Sharp edge: if the index holds custom records and a
run is not passedcustomRecords, those pages are missing from the run's page list
and the exports are not refreshed — the warning now says so, and names
customRecordsas the fix. PassingcustomRecords: []asserts there are none. -
Breaking:
pathPrefixis now a segment prefix in search./docsmatches/docsand
/docs/apiand no longer matches/docsmith. This is a behaviour change for any
prefix that was relying on raw string matching. Note the scope: this covers the
search and section-retrieval paths. Administrative page listing still matches on a
raw string prefix, solistPageswith/docscontinues to return/docsmith. -
results: nullfrom a search endpoint is an error, not an empty result set. The
client and thecreateSearchstore validate the response shape; on a 2xx response a
nullbody, a parseable error page, orresults: nullraisesInvalid search response. A rejected response is never written to the result cache. -
Title and anchor ranking keeps non-ASCII letters. Matching is Unicode-aware and
NFC-normalized, so Japanese, Cyrillic, Greek and accented-Latin titles get the
lexical boosts. Accents are preserved rather than folded, so a query forcafeno
longer matches a title ofcafé. -
Chunk metadata is budgeted in UTF-8 bytes against Upstash's 48 KB per-record
limit, measured on the exact JSON that will be sent rather than in characters. The
trimmable text field is truncated to fit with a warning; if the required fields alone
do not fit, the run fails naming the page URL and the offending field. -
Raw
.sveltecontent-filesmode honours noindex/weight/ignore signals
statically, and withholds a page when a recognised signal is present but not
statically decidable — an expression, inside an{#if}, or a spread the scanner can tie
to a protective attribute. Reading an undecidable protective signal as false would
publish a page someone asked to hold back. Known gaps, pinned by tests:
{@html}/{@render}contents are not extracted,+layout.sveltesignals do not
propagate to the pages beneath, and a conditionaldata-search-ignoredrops its subtree
without withholding the page. -
Breaking: the
sveltepeer range is now^5.20.0. The generated templates use
$props.id(), which landed in Svelte 5.20.0; the previous^5.0.0allowed versions
that cannot compile them.
Fixed
- The relevance playground returned 500 for every error. It read
error.statusCode,
butSearchSocketErrorexposesstatus, so intended 4xx responses were reported as
server errors. - Documentation corrections throughout: browser examples import
searchsocketScrollToText
fromsearchsocket/scrollrather than the server-onlysearchsocket/sveltekit
barrel; the quickstart installssearch-triggeras well assearch-dialog; the
page-retrieval route is/api/search/pages/<path>, nested underapi.path; hook
examples use the realExtractedPageandIndexStatsshapes; the removed
embedding.*keys no longer appear in a config example;scoreGapThresholdis
described as adjacent-gap trimming, which is what the code does; scores are no longer
described as a 0–1 confidence scale; the Upstash Vector feature claim says hybrid
retrieval with DBSF fusion rather than reranking and input enrichment;SECURITY.md
lists the supported versions and the anonymousmcp.handle.access: "public"mode.