Skip to content

v4.0.0_sync

Choose a tag to compare

@axwfae axwfae released this 14 Sep 13:42
· 142 commits to master since this release
fb5cfe8

DonSeTch v4.0.0_sync

The agent web stack, rebuilt end to end. Same four tools, same
zero-config donsetch mcp default. Faster, stealthier, more
reliable, and honest about what it knows.

Removed

  • web_answer (the evidence pack) and web_memory (the local vector
    store), by my call. A fixed search-then-read pipeline and a
    memory-of-everything store strip the agent's control over the loop
    (which link, which budget) and spend tokens on pages the agent
    would not have picked. The agent owns search + fetch; DonSeTch just
    makes each of those calls god-tier. The search prefetch that the
    pack spawned is kept (web_fetch consumes it: a speed win with zero
    new surface).

Fixed

  • Persona locale is fail-closed sanitized before it can reach Chrome
    --lang, CDP navigator.languages injection, or an
    Accept-Language header. A corrupt on-disk persona or hostile
    persona.locale / LANG value can no longer break out of the JS
    array or inject header bytes; invalid values become en-US.
  • A warm ghost whose viewport/locale no longer matches the incoming
    persona (quarantine re-mint) now relaunches instead of silently
    claiming the old identity. web_screenshot uses the same persona
    wire as tier-1.
  • Cross-process host-pace rows are compared by host on read (FNV
    collision no longer shares a floor) and stale .tmp files from
    failed atomic writes are pruned.
  • PDF glyph walk no longer panics when the first glyph's font has no
    PDFium-reportable name (Type3, missing BaseFont, over-long name).
    The mono-font check now bounds-checks the family index like the
    sibling dingbat flag; a crafted PDF can no longer abort the process
    (release panic = "abort") on fetch. (dondai44423#221)
  • Search syndication dedup keeps punctuation in the title key, so
    C++ Tutorial and C# Tutorial (and dotted versions like
    Rust 1.75) no longer collapse into one result and silently drop
    the other. Only titles that are identical after case/whitespace
    normalization still dedup. (dondai44423#222)
  • web_screenshot omitted full_page now defaults to viewport on
    both CLI and MCP (MCP used to silently mean full-page).
  • Fetch CLI long-help cites --actions, not the dead
    --browser-actions.

Changed

  • donsetch config show redacts bearer tokens and credential-bearing proxy
    values, including proxy pools, while still showing whether each field is
    configured and where its value came from.
  • Browser version probes now apply one deadline to process exit and bounded
    stdout capture, terminate inherited-pipe descendants on Unix and Windows,
    and cap captured output at 64 KiB.
  • Every runtime env read now flows through the typed config. The old
    env names keep their exact historical trigger semantics and still
    work in 4.0, but they are deprecated; donsetch doctor lists the
    ones your shell still sets and maps each to its config key. The
    hard cut lands in a later minor once the migration is done.

Added

  • Egress fabric (v4 A): search, crawl, fetch, and ghost share one
    process-wide proxy pool with durable lane health. Burned and dead
    lines survive restarts in egress-health.json (kill:
    DONSETCH_NO_EGRESS_PERSIST). When a pool is configured, web_fetch
    sticks to one exit per host and rotates on 429 / 407 / connect-dead /
    timeout; the lane stays pinned for the whole redirect chain (never
    mid-200-session). Kill: DONSETCH_NO_FETCH_ROTATE. Personas get an
    exclusive lane bind: a burned or foreign-persona exit is never
    reused for a new mint, and quarantine frees the lane. Per-lane RTT
    EWMA feeds search pacing and surfaces in donsetch doctor --deep
    as one line per lane (ok / slow / burned / dead / auth) with a named
    fix. Daemon preflight still benches dead proxies before the first
    query; crawl skips benched lanes and reports outcomes into the same
    health world.

  • Learning engine v2 (v4 B, local only): per-(engine, intent)
    search trust EWMAs drive roster order (search-trust.json v2; empty
    intent maps seed every intent from engine-global history so a
    restart never re-learns a walled engine from zero). Domain profiles
    stamp which egress class their cookies were learned on; a Warm
    clearance vault is refused when the live lane is the other class
    (home-IP cookies never ride a proxy exit). Crawl host ladders
    (429 storms, robots delays) persist to crawl-governor.json
    (cap ~2k hosts, 7d TTL). Enrich/prefetch success density feeds a
    capped domain quality prior (search-quality.json, ±0.08 after
    ≥3 samples; kill DONSETCH_NO_QUALITY_PRIOR; walls and timeouts
    never count). Agent-outcome feedback (must_contain miss / thin /
    SoftNotFound) soft-demotes class|host in outcome-feedback.json
    (±0.05 after ≥2 misses; default off until the 24h soak proves
    it; enable with DONSETCH_OUTCOME_FEEDBACK=1; never extra fetches).
    donsetch status has an improve line
    (warm-hits, walled, cooldowns, flaky, low-trust/quarantined,
    quality hosts, outcome demotes) and donsetch doctor --improve
    explains the loop in ~10 lines with live local receipts and kill
    switches. No MCP tool, no telemetry, no cross-machine sharing.
    A 24h soak battery still gates any public improve claim.

  • Search v3: adaptive early-return cancels stragglers once ≥3
    independent index families already agree on a top-3 URL (kill
    DONSETCH_NO_SEARCH_EARLY). Mojeek empty-parse (blocked as HTTP
    200) burns engine trust harder so it leaves the default width
    instead of occupying a slot every query. Compact snippets trim to
    180 chars. Query compiler splits site: / filetype: / intitle:
    out of the free text and only sends each operator to engines that
    honor it (DDG lite strips them so BM25 never ranks the literal
    token); post-merge filters enforce site: and intitle:, and
    filetype: keeps matching URLs plus extensionless download
    handlers (kill DONSETCH_NO_QUERY_COMPILE). site:github.com,
    site:stackoverflow.com, and friends also join the fan-out as
    that site's own API. A byte-derived SERP instant layer lifts
    featured snippets / instant answers / knowledge panels out of the
    organic list into their own slot with the source URL always
    present (never invents text; not cached; kill
    DONSETCH_NO_SERP_INSTANT). Code verticals are query-gated and
    stress-aware: StackExchange only on Q&A/error language, MDN only
    on web-platform tokens, HN only on release/announce; under pool
    stress HN drops first.

  • Doctor ultra (v4 D): broader local coverage, same fast default.
    New checks: config posture (NO_CONFIG_FILE + DONSETCH_CONFIG
    conflict, missing explicit file, layer report), search health
    (engine trust, quarantine, quality/outcome receipts, BYOK key
    state counts, C kill-switch flags), clearance stores (routes.json
    counts, handles, cookie vault), crawl stores (governor host
    ladders, page-history size warning past 5MB), DNS resolution
    (independent of HTTP, names AAAA presence), and a captive-portal
    probe under --deep (generate_204 must stay 204). State
    permissions now cover every secret-bearing store
    (ghost-state.json, routes.json, byok-keys.json) and
    auto-tighten to 0600. All new checks carry unit tests; live
    doctor on a real box is green with honest warnings.

  • ML-DSA ClientHello parity (v4 E1): Chrome 151 pads
    signature_algorithms with ML-DSA 44/65/87 (IANA 0x0904/05/06).
    boring 5.2.0 finally names and verifies those codes, so the
    ChromeTrue wire advertises them (mldsa44:mldsa65:mldsa87: at
    the front). InterceptionSafe never sends them (a corporate MITM
    re-terminates without ML-DSA certs). Kill:
    tls.mldsa_sigalgs=false / DONSETCH_NO_MLDSA_SIGALGS.

  • Persona wire divergence (v4 E2): per-domain personas now
    actually drive the wire. Tier-1 Accept-Language is persona-locale
    first (TLD/script kept as a lower-q preference so localized
    content still lands, without the classic en-US browser speaking
    perfect ru-RU JA4H tell). The ghost browser launches with the
    persona's viewport and --lang, and CDP navigator.languages
    matches. Quarantined personas fall back to the default wire.

  • Crawl polish (v4 F): dataset JSONL rows carry
    dataset_version: 1 in structuredContent; resume-token TTL is
    fetch.resume_ttl_secs (default 7200, clamped 300..86400) instead
    of a hardcoded 2h. Cross-process host politeness: crawls share a
    per-host last-stamp file under <cache>/host-pace/ so a daemon and
    a CLI crawl of the same host respect one floor gap (kill:
    DONSETCH_NO_HOST_PACE_FILE; corrupt file = ignore). Canonical/
    binary skip and transient-vs-permanent retry were already on
    master; reconfirmed with tests.

  • just win-check: type-checks the crate for x86_64-pc-windows-gnu
    from Linux (clippy, no linkage), both --no-default-features and
    the full feature set, so #[cfg(windows)] breakage from a
    Linux-only change surfaces before the push instead of in Windows
    CI. Needs mingw-w64; see CONTRIBUTING.md.

  • One typed runtime config (src/config.rs, issue dondai44423#193): every knob
    lives in a layered struct, defaults < donsetch.toml
    (DONSETCH_CONFIG or <config-dir>/donsetch/donsetch.toml,
    skippable via DONSETCH_NO_CONFIG_FILE=1) < DONSETCH_<SECTION>__<KEY>
    env names. Values validate loudly at load; unknown TOML keys name
    the offending file. donsetch config show prints each knob with
    its value and origin (--markdown for the reference table,
    --legacy for the old-name map); donsetch doctor warns about
    deprecated env names active in the shell.

  • Resurrection reaches transport-dead URLs: a TLS handshake against
    a parked domain, a dead DNS name and a refused port now gate
    archive=auto resurrection the same way 404/410 do, while
    timeouts, resets and protocol errors stay excluded (a snapshot
    must never launder an unknown, and a reset can be an IP-level
    block that an archived copy would paper over). The gate reads a
    new structuredContent.fetch_error transport class recorded on
    every status-0 fetch error.

  • Wayback snapshot serving survives wayback's own redirect
    interstitials: a thin capture is re-checked against wayback stub
    markers, and a stub chain-hops through up to 4 <meta http-equiv="refresh"> targets when (and only when) wayback
    rewrote the target (web.archive.org/web/<14-digit-ts>/...); a
    live-web refresh target is never followed, because the URL we
    are resurrecting may still be dead, moved, or hostile.

  • web_screenshot MCP tool: a rendered PNG of a page through the
    existing tier-2 browser (url, full_page, wait_ms). The capture is
    in-process only; the MCP result carries an image content block and
    a text note. Verified live against a real render (PNG header,
    image/png). The CLI ships the same render now that it was wired up:
    donsetch screenshot URL [--full-page|--wait-ms N] prints the
    receipt and, with the CLI-only --out PATH, writes the PNG with
    honest failure paths (bad path or missing path = stderr + exit 1).

  • h3 lane hardening (mnaza, PR dondai44423#169): every transport now exits
    through one point, so decompression and wall detection also apply
    to HTTP/3 responses (a challenge served over h3 no longer reports
    as clean content); alt-svc routes honor the server's own ma=
    lifetime instead of a constant; the HTTP/3 body reader stops at
    the shared 64 MiB cap before allocating; routes.json carries
    serialized TLS session material and lands owner-only (0600).

  • Experimental HTTP/3 lane (opt-in via DONSETCH_H3=1) on a quiche
    0.29.3 fork that shares one BoringSSL build with tier-1. An h3
    route is learned from alt-svc response headers (same-origin
    only, proxies exempt) and persisted in the cache dir under
    routes.json together with the serialized QUIC TLS session. The
    lane stays off by default: on repeat visits the v1 one-shot
    connection shape measures slower than our reused h2 pool, and
    DONSETCH_NO_H3 kills the whole path regardless. The h3 Client
    Hello reuses the same Chrome-true TLS builder as h1/h2.
    Transport parity verified live against curl --http3 on a
    1.4 MB page (852 ms vs 825 ms) and 0-RTT resumption arms and
    is accepted on repeat visits; the default stays on our reused
    h2 pool, which still measurably wins the same-or-faster gate.

  • Local web memory for agents (web_memory tool + donsetch memory
    query): a bounded on-device index of pages this machine fetched.
    all-MiniLM-L6-v2 quantized runs locally on the same onnxruntime
    the search reranker uses; the model + tokenizer download once,
    sha256-pinned, into the cache dir and nothing ever leaves the box
    after that. Fetches, crawl pages and search snippets ingest
    automatically; DONSETCH_NO_WEB_MEMORY disables ingest and search;
    DONSETCH_WEB_MEMORY_CAP bounds the index (default 4000 rows,
    oldest-first eviction). Unavailable on builds without the rerank
    feature: the release builds all carry it.

  • Adapter registry v1 (donsetch adapters): the named rewrite and
    extract adapters are now a registry, and operators can add more
    fetch-level rewrites as pure-data JSON plugins in
    cache_dir()/adapters/ (one file per rule: hosts, optional path
    prefix, an https target template with one {path} placeholder;
    no executables or scripts, the fetcher's egress guards still
    apply). Bad files are skipped with a receipt and never break
    fetching; DONSETCH_NO_ADAPTERS=1 stays the master kill switch.

  • Native keyless Google search via the legacy mobile endpoint, using
    DonShadow without a browser or paid API. Seven selectable Nokia
    profiles (DONSETCH_GOOGLE_PROFILE, default 6230-03.15), observed
    working in local tests; availability is not guaranteed. An explicit
    CAPTCHA permits one next-profile attempt in the existing thin-merge
    retry wave; successful profiles remain preferred per egress in
    memory, with circular advancement on CAPTCHA and no profile cooldowns.
    Profile selection resets on restart. HTTP 429 and other walls do
    not rotate. Native and
    browser Google share one ranking family but keep separate health.

  • Route memory: fetches learn route health per tier (EWMA latency by
    route, failure classes, LRU-bounded store). Tier 1 rides healthy
    routes only; persistent failures quarantine and a background probe
    re-heals them when the block lifts; a replaced route never re-enters
    while still marked failed. Kill switches + donsetch status line.

  • Persona store: long-lived per-domain identity records (fingerprint
    class, headers, session state) ride inside ghost pages across runs,
    with a coherence checker and a donsetch status line.

  • Stealth scorecard: doctor --stealth grades the tier-1 battery
    (TLS/H2/header classes) with conflict codes; --parity diffs the
    live fingerprint against real local Chromium (JA4/JA3/H2/vector
    agreement). Weekly stealth.yml CI alarm on profile drift.

  • Tier-1 request realism: navigation-class header sets per request
    class (document/rpc/embed), subresource shadow-fetching that
    reproduces real page-load traffic (assets behind the primary fetch),
    and tier-1 cookie persistence replaying returning-device sessions
    across runs. Each subsystem carries a kill switch.

  • Prewarm: search hands a warm tier-1 pipe to the first-result fetch
    that follows it (hot-path time drops from roughly 550 ms wire
    build-up to about 25 ms served from RAM). Served/wire split is
    visible in donsetch status.

  • web_answer: evidence-pack answer tool for chat-first MCP clients
    (query + token budget, up to a handful of pages, ranked citation
    graph, one-line answer). Kill switch DONSETCH_NO_ANSWER_TOOL.

  • Crawl dataset mode: --json renders one JSON object per page
    (url/title/kind/markdown/chars/fetched_at/lastmod/parent), sorted by
    URL, deduped; skipped pages carry reasons. Best for machine-ready
    exports; plain text stays the default.

  • Crawl delta recrawl: --since-last re-fetches pages and compares
    content fingerprints against crawl history; unchanged pages refresh
    history but drop out of results and budgets with skip reason
    "unchanged since last crawl". Replaces the old fixed 24 h window
    that silently missed quiet mutations.

  • Crawl-shape: seeded reader-like frontier ordering (head-window
    jitter over the top ranks, topology preserved), so repeated crawls
    of one site stop replaying an identical mechanical order to access
    logs. Kill switch DONSETCH_NO_CRAWL_SHAPE.

  • Adapters reach the crawl fetch path: the same rewrite pass
    web_fetch uses rides inside crawl fetches; the canonical URL stays
    the dedup/history key.

  • Ghost browser pool: up to 16 warm browser slots keyed by persona
    identity and host affinity; a repeat hit on the same host lands on
    the browser that already carries that site's session state, and a
    persona switch inside a slot relaunches instead of inheriting
    another identity's fingerprint state. donsetch status shows the
    warm-serve receipt; DONSETCH_GHOST_POOL_SLOTS sizes the pool
    (default 3), DONSETCH_NO_GHOST_POOL reverts to the old
    single-slot path.

  • MCP compat folding applies to web_search too: structured-content-
    only clients receive raw result URLs through the folded metadata
    block instead of losing them; the model-facing contract text now
    states it. (dondai44423#165)

  • Clarify compact search labels: counts describe search-index families
    that returned a URL, not independent sources corroborating its
    claims. The weak results message now refers to cross-index
    agreement. Ranking is unchanged. (dondai44423#166)

Changed

  • Crawl pacing cleanup: the vestigial 0-100 ms skim dwell is deleted;
    self-inferred waits cap at 7 s while host-declared waits (Retry-
    After) stay uncapped and honored. All pacing lives in the crawl
    governor, pressure-adaptive.
  • Dependencies: tokenizers 0.23.2, encoding_rs 0.8.40, brotli 9.0.0,
    zstd 0.14.0, psl 2.1.231, actions/checkout 4 -> 7.

Fixed

  • Security review batch (dondai44423#216-dondai44423#219):

    • donsetch doctor Bright Data zone probe hit /zone/route_ips twice
      (a doubled path that 404'd every configured zone). The ship path now
      passes the API root and builds the URL through one shared helper.
    • h3/QUIC dial now applies the same connect-time SSRF filter as h1/h2:
      a DNS rebind to a private/loopback address between the request-time
      check and the dial is refused. DONSETCH_ALLOW_PRIVATE_EGRESS still
      works as the explicit escape hatch.
    • BYOK provider error bodies are capped at 600 chars before they reach
      the agent, stderr, or the debug log (context DoS). serpapi additionally
      scrubs the URL-borne api_key from a reflected error body (key leak).
    • Crawl resume tokens that are not plain ASCII alphanumeric are refused
      before they index the filesystem, closing a path-traversal read-then-
      delete on agent-supplied resume.
  • Browser backend aliases now share the typed enum as their single registry
    and normalize casing and surrounding whitespace across config sources.
    Unknown effective legacy values fail closed instead of silently becoming
    auto; a valid higher-precedence modern setting still overrides them.

  • The ONNX reranker now consumes the validated search.rerank_threads value
    directly instead of leaking and reparsing a string, and reports configured
    values as coming from the layered config. 0 = auto behavior is unchanged.

  • Master hardening wave (overnight full-tree audit, every finding
    reproduced before fixing):

  • h1: a connection that closes before Content-Length is satisfied now
    fails the transport instead of returning the partial body as
    success. The old reader scored truncated pages clean and stored
    them in the revalidation cache as Fresh, so every later fetch of
    that URL served the truncated page forever. The fix caught a lying
    Content-Length in my own egress-proxy test rig live (it claimed 12
    bytes and sent 10).

  • h3: a QUIC connection that closes or drains mid-body is an error,
    same truncated-success class. The route drops and h1/h2 answer.

  • Cookies: a hostile Set-Cookie with a multibyte month token in
    Expires= panicked the date parser, and with panic=abort in
    release that is a one-request remote kill of the daemon. Date
    tokens now parse by bytes. Cookie path matching uses the URI path
    only (a path-scoped cookie used to stop attaching on query URLs,
    against RFC 6265 and every browser). The jar enforces the RFC 6265
    section 6.1 bounds (4096-byte cookies, 100 per domain, 3000 total,
    4 KiB / 50-pair Cookie header), so a Set-Cookie flood can neither
    grow the jar for process lifetime nor emit megabyte headers.

  • Ghost session vault: a live daemon's state save no longer erases
    fresh harvests or resurrects logged-out sessions. Vault writers
    (the logout clear, the session store, the tier-1 sync) stamp a
    vault epoch, and every save adopts whichever side wrote last.

  • Crawl resume-only (empty url + resume token) consumed the token
    twice, always failed with "resume token expired or unknown", and
    destroyed the saved state in the process. One take, and the
    resume-only flow works.

  • Search hot paths no longer initialize the reranker: the first
    rerank::active() on the search path blocked a tokio worker on
    the whole init chain, up to a 120-second model download. Report
    stamps now read a non-initializing check; ranking still
    initializes inside its own spawn_blocking.

  • h2: a bodyless response whose header block arrives fragmented
    (END_STREAM on HEADERS, END_HEADERS on CONTINUATION) no longer
    hangs to the 30s timeout; a missing or unparseable :status is a
    transport error instead of a success with status 0; an
    unfragmented header frame honors the same 256 KiB block cap as
    continuations; response header names are lowercased like h1 so a
    nonconforming peer's mixed-case content-encoding cannot skip
    decompression and its alt-svc cannot skip h3 discovery.

  • TLS: brotli certificate decompression is bounded by a take() cap;
    a hostile compress_certificate stream used to decompress to its
    full (potentially gigabyte) end before the declared-length check
    fired.

  • Wall detection: bare prose mentions of PerimeterX, Imperva,
    Incapsula, Sucuri and Wordfence on healthy 200 pages no longer
    score Challenge (each false positive burned a warm retry, route
    memory and a paid bypass call). The challenge-specific markers and
    error-status co-signals still fire, pinned by tests.

  • Revalidation cache: Cache-Control: no-cache is honored (stored,
    never served fresh); entries are keyed by cookie lane, so a
    jar-less search lane can never be served a logged-in fetch's body
    or the reverse; a 304 whose entry was evicted mid-flight fails
    honestly instead of scoring an empty body as Blocked.

  • Crawl: a redirect out of the seed's host, scope, or robots rules
    is skipped instead of landing in results, dataset and history
    under its final URL. Frontier normalization re-encodes query
    strings (distinct URLs with encoded &/= collided in the
    seen-set and one of them silently never fetched). A hostile
    <priority>NaN</priority> can no longer poison a resume token.
    Too-deep items are skipped with a reason instead of aborting the
    whole crawl and discarding the frontier. A host's declared
    Crawl-delay is honored in full above the 7s self-inferred cap (a
    host declaring Crawl-delay: 30 actually sees 30s pacing), stored
    per host so concurrent crawls stop cross-polluting each other's
    pacing. The governor prunes idle per-(host, lane) clocks instead
    of growing them forever.

  • MCP supervisor: a failed replay write followed by new client data
    no longer drops the held bytes; the held request folds into the
    new replay window.

  • MCP tools: the fetch batch honors cancellation (a cancelled
    12-URL batch no longer runs every escalation to completion);
    web_screenshot honors deadline and cancellation with a 60s
    ceiling; an all-failed fetch batch classifies permanent vs
    transient like the search batch instead of always claiming
    "safe to retry"; a hostile multibyte wayback timestamp no longer
    panics a byte slice; oversized stdin request lines are bounded
    and answered with a parse error instead of growing the reader
    without bound.

  • Search: SERP bodies cap at 3 MiB before the DOM parse (one broken
    proxy answering 200 with tens of MB stalled the whole fan-out
    past its deadline). BYOK results carry single-flight, so two
    concurrent identical queries bill the metered provider once, and
    the byok cache stores the provider's full top-12 (a first search
    at max=2 no longer serves later max=10 calls a 2-row slice as
    cached). Provider error bodies cap at 600 chars. + survives
    cache-key normalization, so "rust vs c++ performance" and "rust
    vs c performance" stop sharing one cache entry. The search cache
    serializes to disk outside its lock. The ddg_html retry lane
    reads its learned trust. Short API keys are never printed whole
    by keys list.

  • Adapters: the JSON fast path honors focus/toc/must_contain/section
    (must_contain on an adapter-shaped URL used to hand the agent the
    full document against its own contract). PyPI normalization
    follows PEP 503 separator runs. The plugin loader opens the file,
    stats the handle and bounds the read: a swapped FIFO could hang
    the first rewrite and a swapped file bypass the size cap.

  • h3 lane: DNS resolution runs off the reactor with the same 10s
    bound as the h1/h2 path; the platform trust store parses once per
    process instead of per request; the profile's accept-encoding
    rides the h3 request (bodies arrive compressed and the wire
    matches Chrome); a server-declared alt-svc ma= is capped at 30
    days; TLS session warm detection uses the same key the session
    store writes, so TFO actually arms on non-default ports and
    proxied lanes.

  • Hygiene: handles.json is written owner-only (interned link URLs
    can carry query-string credentials); finished shadow-burst
    handles are reaped in daemons; timed-out CDP calls release their
    pending slot (a wedged tab used to leak one sender per call); a
    Page.navigate failure surfaces its real cause instead of a
    generic 20s timeout; egress dead-proxy and auth-fail reports use
    poison-recovery locks; config file-layer errors stop labeling
    themselves "invalid env value"; a non-UTF-8 modern env value
    warns instead of vanishing; DONSETCH_CONFIG="" is treated as
    unset; donsetch help no longer routes the removed answer/memory
    commands; the search help lists the real intent verticals.

  • HTTP bearer configuration now fails closed: modern TOML/env tokens reject
    whitespace and non-visible bytes without echoing the secret, while legacy
    text tokens retain their exact historical value instead of being trimmed
    into disabled authentication. CORS/auth validation also runs before daemon
    work.

  • donsetch config show --markdown now emits one complete Markdown table per
    config section, so section labels no longer turn the following knob rows
    into plain paragraphs.

  • Modern DONSETCH_<SECTION>__<KEY> variables now fail loudly when a
    recognized value is not UTF-8 or when distinct names normalize to the same
    config key. Diagnostics identify the variable names without exposing their
    values, and collision handling no longer depends on environment iteration
    order.

  • Typed config validation now rejects an out-of-policy TOML value with
    file attribution even when a valid higher-precedence env value would
    otherwise hide it; legacy out-of-range values remain warning-only.

  • Browser path overrides now preserve source presence across the typed config:
    legacy empty values keep their historical override semantics, while modern
    empty values reset to discovery or ambient defaults. Explicit Chromium,
    CloakBrowser and Playwright paths remain literal.

  • Legacy route-memory and bypass-cache controls compose in their original
    order under the typed config: the route-memory kill switch beats read-only,
    while a valid legacy bypass TTL applied after DONSETCH_BYPASS_CACHE=0
    re-enables the cache (and TTL zero disables it). TOML and modern env
    overrides retain their higher precedence.

  • The supervisor's replay now survives a crash loop: the bytes
    replayed into a replacement were cleared from the unacked
    history, so a second silent death dropped them (reported by
    mnaza in dondai44423#202, discriminating two-death test included). The
    live unacked history is also bounded to the same 1 MiB replay
    window on every append (keep the tail), not only at death time.

  • The browser probe timeout now kills the whole process group, not
    just the parent: a wedged browser whose descendant held the stdout
    pipe used to hang donsetch doctor forever. The probe now fails
    at the timeout, bounded, on every platform.

  • The MCP supervisor no longer loses a request buffered into a
    child that dies before consuming it. The write succeeds while
    the child is alive, so no EPIPE ever fires, and the death only
    surfaces on the next idle poll: the supervisor now replays the
    child's whole unacked history (bounded to 1 MiB, duplicated
    delivery preferred over a lost request). Caught by the macOS CI
    run timing the crash between a successful write and the idle
    poll.

  • Crawl resume tokens survive concurrent crawlers. The store was
    one shared JSON map saved with load-modify-save, so two
    processes issuing tokens at the same time (the daemon plus a
    CLI run, parallel test processes) wrote stale copies over each
    other and fresh tokens read back as expired or unknown. Tokens
    are now one immutable file each under the cache dir; the legacy
    single-file store migrates on first touch, retires only after
    every entry lands, and the consume-on-resume semantics are
    unchanged. Caught by the Windows CI run while it beat on the
    shared store in parallel.

  • donsetch doctor --deep no longer reports a valid Bright Data
    dynamic-IP unlocker (Web Access API zones and friends) as broken:
    the free zone probe costs nothing, and where the zone has no
    static route pool Bright Data answers 403 "Static routes not
    found". That answer no longer reads as a failed check: the
    probe skips with an honest, zero-credit note instead (reported
    by tripflex on dondai44423#200). A 401 always stays a failure.

  • Resurrection no longer claims "never archived" on shaky ground:
    the availability API is lossy and scheme-strict (a capture
    recorded under http:// is invisible to an https:// query), so
    an empty answer now falls through to the complete CDX index
    (scheme-canonical, filter=statuscode:200, last 5 captures)
    before archive=only reports anything, an unreachable archive
    answers transient with its own message instead of a false
    permanent, and a found-but-unusable snapshot names itself and
    its stage (structuredContent.archive_stage) instead of
    collapsing into the live error.

  • site: queries no longer leak off-domain results through BYOK
    providers (issue dondai44423#190): both BYOK exits (provider-first and the
    local-first fallback) sweep results through the same post-merge
    domain filter the local engine uses; goto/redirect proxies drop
    (fail closed), and the warm body prewarm runs on the rows that
    survive the filter.

  • donsetch login --logout DOMAIN now also wipes the rendered-DOM
    cache of the logged-out domain (Mart-Bogdan, PR dondai44423#188): a page
    fetched behind the session was a fourth persisted copy of the
    session, served back with no network hop for up to five minutes
    after logout. Unrelated domains' renders stay.

  • Repeat BYOK searches now ride the same TTL'd disk cache as keyless
    results instead of re-billing the provider (issue dondai44423#195): same query

    • intent replays from cache under a separate byok namespace, capped
      at 500 entries like the local path (mnaza, dondai44423#197).
  • The web-memory index persist stages to a PID+sequence-unique
    file per write (PR dondai44423#191): overlapping persists (a crawl fires
    one per 256-row chunk and one on completion) can no longer tear
    index.json through a shared tmp inode and parse-fail the next
    recall to an empty index.

  • Subresource shadow-fetching no longer aborts the daemon on a page
    containing İ, K or Ω. The scanner searched a to_lowercase()
    copy of the document for tag offsets and then sliced the original
    string at them, but to_lowercase is not byte-length preserving, so
    one such character shifted every later offset: assets silently
    dropped where the shift landed on ASCII, a mid-codepoint slice panic
    where it did not : and the release profile's panic = "abort" turns
    that into a daemon kill. Folding is to_ascii_lowercase now, which
    is byte-length and char-boundary preserving, and is also what the
    HTML standard specifies for tag and attribute names. attr() had
    the same latent pattern and is fixed with it.

  • MCP text-only fold now covers OpenCode v1 (tested on 1.18.3):
    unlike Claude Code / VS Code, OpenCode renders the content array
    and drops structuredContent entirely, so agents previously lost
    every compact state field (URL handles, next_offset, verdicts,
    error codes). Handshake detection now matches opencode and folds
    the state into the leading [meta] text block, same as the other
    text-only clients.

  • A corrupt local embedding model no longer wedges every subsequent
    fetch. ensure_file recursed into itself with identical arguments
    when the file on disk failed its SHA/size pin, never removing it, so
    the same bad bytes were read forever : a stack overflow, or an
    infinite loop if the recursion was optimized into a tail call. The
    bad file is removed and the existing atomic download path takes
    over; a concurrent remover is tolerated with a receipt, any other
    removal failure is honest and immediate.

  • Default-feature builds compile again (Mart-Bogdan, PR dondai44423#174): the
    web-memory status receipt tested the rerank feature with a runtime
    cfg! so no-rerank builds failed the compile on three symbols
    behind the feature gate.

  • donsetch login --logout now wipes the tier-1 echo of the vault
    from ghost-state.json, not only the registries the sessions
    replays through on the next start (issue dondai44423#173).

  • routes.json was rewritten on every alt-svc sighting, could grow
    without bound, and had no switch: re-vouches with nothing
    materially new (under a 60 s grace) are skipped before the disk
    write, expired rows never survive a persist, the row count caps at
    512, and DONSETCH_NO_ALT_SVC shuts the bookkeeping off (issue
    dondai44423#175).

  • Web memory re-embedded and rewrote its whole index once per row,
    putting up to one 20MB+ write on the answer path per hit: batch
    ingest embeds in one pass and persists once, the crawl path chunks
    at 256 rows, and ingestion runs on the blocking pool so a recall
    never holds a response past its deadline (issue dondai44423#178).

  • CI runs now concurrency-cancel per pull request only, never on
    master (Mart-Bogdan, PR dondai44423#181).

  • The alt-svc kill-switch test no longer excludes Windows
    (Mart-Bogdan, PR dondai44423#189): the env-var mutations in it are not
    Windows-specific, so the guard was dead weight.

  • Xvfb reuse gate now demands a bounded real-protocol answer
    (xdpyinfo within 2s) before handing a display to the pool, so a
    SIGKILLed Xvfb's tombstone socket can no longer wedge every
    tier-2 escalation behind an honest "devtools ws timeout".

  • Ghost pool spill fix (mnaza): a same-persona pool previously
    funneled every distinct host into slot 0, since "any warm
    same-persona slot" outranked free slots and the daemon always runs
    one persona. Slot claims (persona key + host) are now stamped and
    visible to concurrent selectors at pick time under the meta lock,
    before the seconds-long browser launch, so a second in-flight host
    claims a free slot and a same-host job joins the in-flight claim
    and warm-serves it. An exhausted pool reuses our coldest own
    browser before evicting a stranger's. Their contribution lives as
    its own rebased commit on master (af13034).

  • Search pacing uses cancellation-safe per-engine/egress admission;
    waiting is included in attempt deadlines and cancelled waiters
    leave no future-slot debt. Google HTTP health is isolated from
    legacy browser health; Google follows common failure and quarantine rules.

  • Search retries retain completed peers when another retry times out
    and report retry timeouts explicitly, with the attempted Google
    profile when available. At most one retry per engine per search. Ordinary
    retries, including Google, keep a three-second budget including pacing.

  • dondai44423#164 audit wave (S1-S6 in the search/fetch stack):
    S1: version matching is boundary-aware; "5.2" no longer matches
    "15.2" or "5.20" but still matches "5.2.1" and "v5.2".
    S2: empty vertical results report no-results instead of success;
    engine OK counts and retry/cache gates were inflated.
    S3: plugin-hit truncation before URL dedup dropped, so oversize
    plugin output can no longer shrink the final unique count.
    S4: cache entries keep engine reports; cache hits return real
    engine evidence (old 4-tuple caches still load).
    S5: single-URL fetch with budget_tokens runs under
    run_with_budget: deadline_ms and MCP cancellation apply, and
    the budget bounds the page like batch mode.
    S6: BYOK plugin error envelopes cap at 600 chars, matching the
    stderr trim.

  • Storage guard: bounded target-dir growth and pinned the cargo
    profile on every nextest run.

  • Ghost-state counters now merge against the state on disk at every
    save, so a late save with a stale in-memory snapshot can no longer
    rewind lifetime counters (caught live: the pool warm-serve receipt).

Full Changelog: https://github.com/axwfae/donsetch_noavx/commits/v4.0.0_sync