// Map route — 藏書地圖(library-map SDD M2;源頭 Arcrun#39)。 // 聚合 SQL 只准住基本盤(D6 推論,design §2 關鍵歸屬裁定)——本 route 是薄殼,SQL 全在 // actions/library-map.ts。auth 照 base route 既有慣例:raw worker 不驗 key,owner 隔離由 // cypher proxy(X-Arcrun-API-Key → owner_id 注入)/caller 帶 owner_id 參數完成。 import { Hono } from 'hono'; import type { Bindings } from '../types'; import { getLibraryMapDetail, listLibraryMaps, recomputeLibraryMap } from '../actions/library-map'; export const mapRoutes = new Hono<{ Bindings: Bindings }>(); // POST /map/recompute?library=X — 對該庫重算地圖(ingest 尾端/backfill 呼叫;R2 增量重算)。 // body(皆選填):{ narrative, commit_hash, owner_id, source_prefix, triplet_template, top_n } // - narrative:本輪由 caller 傳入(wiki 首段抽取屬 ingest 端 M3,base 不抽取)。 // - source_prefix:舊 triplet(無 library slot 值)的 source_uri 前綴 fallback(M3 backfill 前過渡)。 // 寫入順序安全:先建新 active map block,再把舊 block 標 superseded(見 action 註解)。 mapRoutes.post('/recompute', async (c) => { const body = (await c.req.json().catch(() => ({}))) as Record; const library = c.req.query('library') || (typeof body.library === 'string' ? body.library : ''); if (!library || !library.trim()) return c.json({ success: false, error: 'library required' }, 400); try { const result = await recomputeLibraryMap(c.env.DB, { library, narrative: typeof body.narrative === 'string' ? body.narrative : undefined, commit_hash: typeof body.commit_hash === 'string' ? body.commit_hash : undefined, owner_id: typeof body.owner_id === 'string' ? body.owner_id : c.req.query('owner_id') || undefined, source_prefix: typeof body.source_prefix === 'string' ? body.source_prefix : undefined, triplet_template: typeof body.triplet_template === 'string' ? body.triplet_template : undefined, top_n: typeof body.top_n === 'number' ? body.top_n : undefined, }); return c.json({ success: true, ...result }); } catch (e) { return c.json({ success: false, error: e instanceof Error ? e.message : String(e) }, 400); } }); // GET /map — 全館地圖:每庫一行(library+narrative+top 3 entities+triplet_count)。 // 形狀給 MCP instructions/GUI 首頁共用(R3/R4),設計在數百 token 內。 mapRoutes.get('/', async (c) => { const libraries = await listLibraryMaps(c.env.DB, c.req.query('owner_id') || undefined); return c.json({ success: true, libraries, count: libraries.length }); }); // GET /map/:library — 該庫詳圖(完整 slots+可嵌人話 content)。 mapRoutes.get('/:library', async (c) => { const map = await getLibraryMapDetail(c.env.DB, c.req.param('library'), c.req.query('owner_id') || undefined); if (!map) return c.json({ success: false, error: 'not found' }, 404); return c.json({ success: true, map }); });