Access layer · verified root cause

NLWeb /ask cold-start — fixed with a prebuilt index

orank scored NLWeb /ask + streaming as FAIL. It wasn't a scanner bug (I checked, and I was wrong to suspect one): a cold /ask loaded the entire Fumadocs source on init and cold-started at ~3.9s, timing out the probe. This fix removes that load.

Before
~3.9s
cold-start — searchDocs imported the compiled Fumadocs source (all 265 pages) at module init. Crosses orank's ~2.5–3.5s probe timeout.
After
sub-second
searchDocs reads a prebuilt {url,title,description} index (265 entries) — no heavy source in the /ask import graph at all.

How it was verified (self-checked this time)

causeburst of 20 concurrent cold hits → slowest 3.87s / 3.51s / 3.42s (warm 0.63s). /api/mcp at 2.4s cold PASSES orank → timeout sits ~2.5–3.5s.
indexgenerated 265 entries; 15 sampled URLs all resolve 200 on the live site — derivation correct.
graph/ask closure is now source-freedoc-search.ts imports only constants + the generated index.
testsask-streaming + openapi-discoverability 10/10; tsc + biome clean.
verifyfinal proof is the post-deploy orank re-scan — not an assertion.

The change

newscripts/gen-docs-search-index.jslib/generated/docs-search-index.ts (wired into build-plugins.sh, regenerated on build)
splitsearchDocs (index-backed, no source) stays in doc-search.ts; getDocMarkdown (source-backed, full bodies) moves to doc-markdown.ts — used only by MCP full-content.
scopebehavior identical (same DocHit output); only the data source changed. /api/mcp keeps source for full markdown (it passes at 2.4s).