Skip to content

v0.11.0

Latest

Choose a tag to compare

@gregpriday gregpriday released this 12 Sep 10:06
· 1 commit to develop since this release

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-files chunks whose text contained HTML entities re-embed once. Raw
    .svelte extraction now decodes entities properly, so that text genuinely changed.

Changed

  • Breaking: the inert embedding.* options are rejected instead of ignored.
    embedding.model, embedding.dimensions and embedding.taskType were 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 join embedding.batchSize and
    embedding.images.enable in 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 point upstash.url /
    upstash.token at that index.

  • A refused large-deletion plan now writes nothing at all. Previously a run stopped by
    indexing.maxDeletionRatio still 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.txt blocks index writes in crawl mode, 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/build read robots.txt from
    the build directory and treat an unreadable file as absent.

  • robots.txt matching follows RFC 9309 for * wildcards, the $ end-anchor, group
    merging across consecutive User-agent lines, and longest-rule-wins specificity. Not
    certified-complete: percent-encoded paths are compared as written, not canonicalised.

  • llms.txt / llms-full.txt are 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 passed customRecords, those pages are missing from the run's page list
    and the exports are not refreshed — the warning now says so, and names
    customRecords as the fix. Passing customRecords: [] asserts there are none.

  • Breaking: pathPrefix is now a segment prefix in search. /docs matches /docs and
    /docs/api and 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, so listPages with /docs continues to return /docsmith.

  • results: null from a search endpoint is an error, not an empty result set. The
    client and the createSearch store validate the response shape; on a 2xx response a
    null body, a parseable error page, or results: null raises Invalid 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 for cafe no
    longer matches a title of café.

  • 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 .svelte content-files mode 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.svelte signals do not
    propagate to the pages beneath, and a conditional data-search-ignore drops its subtree
    without withholding the page.

  • Breaking: the svelte peer range is now ^5.20.0. The generated templates use
    $props.id(), which landed in Svelte 5.20.0; the previous ^5.0.0 allowed versions
    that cannot compile them.

Fixed

  • The relevance playground returned 500 for every error. It read error.statusCode,
    but SearchSocketError exposes status, so intended 4xx responses were reported as
    server errors.
  • Documentation corrections throughout: browser examples import searchsocketScrollToText
    from searchsocket/scroll rather than the server-only searchsocket/sveltekit
    barrel; the quickstart installs search-trigger as well as search-dialog; the
    page-retrieval route is /api/search/pages/<path>, nested under api.path; hook
    examples use the real ExtractedPage and IndexStats shapes; the removed
    embedding.* keys no longer appear in a config example; scoreGapThreshold is
    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 anonymous mcp.handle.access: "public" mode.