Commit graph

2 commits

Author SHA1 Message Date
331e748bcb
cloud ask: unified multi-shard via bucket manifest + read-ahead tuning
`BUCKET_URL` (one env var) → client GETs `clones/manifest.json` →
opens HttpRangeVFS per listed shard → FTS5 across all shards in
parallel (ThreadPoolExecutor; per-thread apsw.Connection) → merge by
BM25 score → pull chunks from the owning shard → LLM + verify.
No per-query --shard-url, no path proliferation.

Two manifests published on s3://arborist/clones/:
  manifest.json       — default: virtback only (2.5MB, ~5s/query)
  manifest-full.json  — opt-in: all 5 shards (35GB, prohibitive over
                        WAN due to FTS5 b-tree walk pattern; needs
                        smaller shards or co-located query proxy)

HttpRangeVFS read-ahead tuned from per-page (4KB) to 64KB block-aligned
cache. Each cache miss fetches one 64KB block; subsequent reads within
the block are local-fast. Lower miss count, similar bytes-on-wire
(64KB amortizes well over typical 4-16 page b-tree clusters; larger
read-ahead like 4MB over-fetches on random FTS5 reads).

Sample run (default manifest):
    make cloud-ask Q="who developed virt-back?"
    → EVIDENCE-WARRANTED · via claim_lattice  1/1  4.71s  (bucket-direct)
       21 HTTP requests · 1344 KB

ACL: genesis full-bench shards flipped to public-read (CC-BY-SA
Wikipedia content). Reachable now if you want to play with the slow
multi-shard path; not in the default manifest because chat latency
matters more than coverage breadth.
2026-05-30 13:33:58 -04:00
3bc5ec1e6c
wallet: bucket-direct queries via SQLite HTTP-range VFS (apsw)
Pure-cloud consumer: client opens an arborist .db file IN PLACE on a
bucket via HTTP RANGE reads, runs FTS5 + SQL locally, fetches chunk
bodies from `blobs/<hash>` on the same bucket. No intermediate server
in the data path. The bucket layout we already produce (Tier A clones
plus --jit-blobs blobs/) is exactly what this consumer needs.

Module `arborist/wallet/bucket.py`:
- HttpRangeFile / HttpRangeVFS: apsw subclasses. xRead → HTTP Range
  GET; xFileSize → cached HEAD. xWrite/xTruncate raise (read-only).
  IOCAP_IMMUTABLE so SQLite skips locking/journaling. Empty tempfile
  backs the apsw VFSFile C-bookkeeping; never actually read.
- _LRUByteCache: thread-safe (offset,length)-keyed LRU; soft byte
  budget (default 32 MB). SQLite's own page cache (~8 MB) handles
  most hot-path amortization, so our LRU is the second-level safety
  net for working sets that overflow SQLite's cache.
- _HttpTransport: stdlib urllib (zero new runtime deps beyond apsw).
- BucketClient: high-level — fts_search / chunks_for_doc /
  fetch_chunk_body / snapshot_root + page-cache stats.

CLI (`arborist cloud <sub>`):
- `cloud search Q --shard-url ...`
- `cloud snapshot-root --shard-url ...`
- `cloud fetch-chunk LEAF_HASH --blob-base ...`

Makefile:
- `make bootstrap-bucket` (installs apsw)
- `make cloud-search Q="..." SHARD_URL=https://.../000.db`
- `make cloud-snapshot-root SHARD_URL=...`
- `make cloud-fetch-chunk LEAF_HASH=... BLOB_BASE=...`
- `make cloud-demo` — end-to-end proof on a vanilla laptop: seeds a
  tiny bucket layout in tmp, serves it via a Range-aware static
  HTTP server, runs all three cloud commands from an isolated HOME
  that has no local arborist data. Asserts laptop HOME stays empty
  start-to-finish.

Tests (tests/test_wallet_bucket.py, 4 passing):
- bucket-direct FTS5 results == direct sqlite3 results
- chunk fetch round-trip + hash verify
- snapshot_root bucket-direct == snapshot_root local
- second identical query adds 0 HTTP requests (SQLite-cached)

pyproject: new `[bucket]` extra carries apsw>=3.45; folded into [dev].
2026-05-30 08:52:23 -04:00