@volter/twin-turbopuffer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +145 -0
  3. package/dist/src/cli.d.ts +2 -0
  4. package/dist/src/cli.js +27 -0
  5. package/dist/src/generated/surface.gen.json +1 -0
  6. package/dist/src/generated/ui.gen.json +1 -0
  7. package/dist/src/index.d.ts +11 -0
  8. package/dist/src/index.js +65 -0
  9. package/dist/src/key-gate.d.ts +3 -0
  10. package/dist/src/key-gate.js +39 -0
  11. package/dist/src/manifest.d.ts +2 -0
  12. package/dist/src/manifest.js +28 -0
  13. package/dist/src/screens/dashboard.d.ts +11 -0
  14. package/dist/src/screens/dashboard.js +191 -0
  15. package/dist/src/semantics/namespaces.d.ts +5 -0
  16. package/dist/src/semantics/namespaces.js +17 -0
  17. package/dist/src/turbopuffer-capabilities.d.ts +6 -0
  18. package/dist/src/turbopuffer-capabilities.js +442 -0
  19. package/dist/src/turbopuffer-conformance.d.ts +8 -0
  20. package/dist/src/turbopuffer-conformance.js +102 -0
  21. package/dist/src/turbopuffer-connector.d.ts +34 -0
  22. package/dist/src/turbopuffer-connector.js +152 -0
  23. package/dist/src/turbopuffer-filter.d.ts +57 -0
  24. package/dist/src/turbopuffer-filter.js +286 -0
  25. package/dist/src/turbopuffer-server.d.ts +23 -0
  26. package/dist/src/turbopuffer-server.js +72 -0
  27. package/dist/src/turbopuffer-stem.d.ts +1 -0
  28. package/dist/src/turbopuffer-stem.js +133 -0
  29. package/dist/src/turbopuffer-store.d.ts +80 -0
  30. package/dist/src/turbopuffer-store.js +1304 -0
  31. package/dist/src/turbopuffer-text.d.ts +44 -0
  32. package/dist/src/turbopuffer-text.js +189 -0
  33. package/dist/src/turbopuffer-twin.d.ts +25 -0
  34. package/dist/src/turbopuffer-twin.js +406 -0
  35. package/package.json +56 -0
  36. package/src/cli.ts +28 -0
  37. package/src/generated/surface.gen.json +1 -0
  38. package/src/generated/ui.gen.json +1 -0
  39. package/src/index.ts +92 -0
  40. package/src/key-gate.ts +39 -0
  41. package/src/manifest.ts +63 -0
  42. package/src/screens/dashboard.tsx +214 -0
  43. package/src/semantics/namespaces.ts +30 -0
  44. package/src/turbopuffer-capabilities.ts +455 -0
  45. package/src/turbopuffer-conformance.ts +101 -0
  46. package/src/turbopuffer-connector.ts +153 -0
  47. package/src/turbopuffer-filter.ts +277 -0
  48. package/src/turbopuffer-server.ts +81 -0
  49. package/src/turbopuffer-stem.ts +104 -0
  50. package/src/turbopuffer-store.ts +1157 -0
  51. package/src/turbopuffer-text.ts +204 -0
  52. package/src/turbopuffer-twin.ts +429 -0
@@ -0,0 +1,455 @@
1
+ // Turbopuffer capability manifest — the vendor's surface as the denominator, authored top-down from
2
+ // the installed @turbopuffer/turbopuffer@2.8.0 (every Namespace method in src/resources/
3
+ // namespaces.ts, every write/query parameter, every filter operator and rank_by form in
4
+ // src/resources/custom.ts, the list endpoint in top-level.ts), not from what this twin built.
5
+ //
6
+ // Every `done` verify drives the REAL `handleTurbopufferTwinRequest` over a FRESH temp root with a
7
+ // pinned clock and asserts VALUES. The verifies were authored but NOT executed in the build that
8
+ // wrote them (the owner's rule for that build forbade running tests); the product-door evidence
9
+ // was a real @turbopuffer/turbopuffer client driving a served twin (README ## Coverage). They are
10
+ // queued for the catalog gate.
11
+ //
12
+ // Turbopuffer is API-first — an integrator calls it from code; its dashboard manages keys and
13
+ // billing — so there is no mirror and no UI capability (README § No UI mirror).
14
+ import { mkdtempSync, rmSync } from 'node:fs';
15
+ import { tmpdir } from 'node:os';
16
+ import { join } from 'node:path';
17
+ import { checkCapabilities, type CapabilityReport, type CapabilitySpec } from '@volter/world-tooling';
18
+ import type { RemoteExecute, TwinAction } from '@volter/world-core';
19
+ import { handleTurbopufferTwinRequest } from './turbopuffer-twin.ts';
20
+ import { checkTurbopufferConformance } from './turbopuffer-conformance.ts';
21
+ import { performTurbopufferAction, syncTurbopufferFromRemote } from './turbopuffer-connector.ts';
22
+
23
+ const AT = '2026-09-23T00:00:00.000Z';
24
+ const AUTH = { authorization: 'Bearer tpuf_capability' };
25
+
26
+ type Res = { status: number; body: any };
27
+ type Tp = {
28
+ root: string;
29
+ req: (method: string, path: string, body?: unknown, headers?: Record<string, string>) => Promise<Res>;
30
+ write: (ns: string, body: unknown) => Promise<Res>;
31
+ query: (ns: string, body: unknown) => Promise<Res>;
32
+ };
33
+
34
+ async function withRoot(fn: (tp: Tp) => Promise<boolean>): Promise<boolean> {
35
+ const root = mkdtempSync(join(tmpdir(), 'turbopuffer-cap-'));
36
+ const req: Tp['req'] = async (method, path, body, headers = AUTH) => {
37
+ const r = await handleTurbopufferTwinRequest({ method, path, headers, root, occurredAt: AT, ...(body === undefined ? {} : { body: JSON.stringify(body) }) });
38
+ return { status: r.status, body: r.body as any };
39
+ };
40
+ try {
41
+ return await fn({ root, req, write: (ns, b) => req('POST', `/v2/namespaces/${ns}`, b), query: (ns, b) => req('POST', `/v2/namespaces/${ns}/query`, b) });
42
+ } finally {
43
+ rmSync(root, { recursive: true, force: true });
44
+ }
45
+ }
46
+
47
+ const ids = (r: Res): unknown[] => (Array.isArray(r.body?.rows) ? r.body.rows.map((x: any) => x.id) : ['<no rows>']);
48
+ const same = (a: unknown, b: unknown): boolean => JSON.stringify(a) === JSON.stringify(b);
49
+ const isError = (r: Res, status: number): boolean => r.status === status && r.body?.status === 'error' && typeof r.body?.error === 'string';
50
+
51
+ const FTS = { title: { type: 'string', full_text_search: { tokenizer: 'word_v2' } } };
52
+ const PEOPLE = {
53
+ upsert_rows: [
54
+ { id: 'p1', title: 'steven tey dub founder', group: 'g1', tags: ['a', 'b'], score: 10 },
55
+ { id: 'p2', title: 'stephanie writer', group: 'g2', tags: ['b'], score: 5 },
56
+ { id: 'p3', title: 'kiran dub dub engineer', tags: [], score: 7 },
57
+ ],
58
+ schema: FTS,
59
+ };
60
+ const all = { rank_by: ['id', 'asc'], top_k: 100 };
61
+
62
+ const done = (id: string, area: string, title: string, tier: CapabilitySpec['tier'], verify: CapabilitySpec['verify'], dimension: CapabilitySpec['dimension'] = 'api'): CapabilitySpec =>
63
+ ({ id, area, title, dimension, tier, expected: 'done', verify });
64
+ const todo = (id: string, area: string, title: string, tier: CapabilitySpec['tier'], dimension: CapabilitySpec['dimension'] = 'api'): CapabilitySpec =>
65
+ ({ id, area, title, dimension, tier, expected: 'todo' });
66
+
67
+ export const TURBOPUFFER_CAPABILITIES: CapabilitySpec[] = [
68
+ // ── write ────────────────────────────────────────────────────────────────────────────────
69
+ done('turbopuffer.write.upsert_rows', 'write', 'write upsert_rows stores documents that a query returns with their attributes', 'core', () => withRoot(async (tp) => {
70
+ const w = await tp.write('ns', PEOPLE);
71
+ const q = await tp.query('ns', { ...all, include_attributes: ['group', 'score'] });
72
+ return w.status === 200 && w.body.status === 'OK' && w.body.rows_upserted === 3 && w.body.rows_affected === 3
73
+ && same(q.body.rows, [{ id: 'p1', group: 'g1', score: 10 }, { id: 'p2', group: 'g2', score: 5 }, { id: 'p3', group: null, score: 7 }]);
74
+ })),
75
+ done('turbopuffer.write.upsert_replaces_document', 'write', 'an upsert replaces the whole document (attributes it omits are gone)', 'core', () => withRoot(async (tp) => {
76
+ await tp.write('ns', { upsert_rows: [{ id: 1, x: 'one', y: 'two' }] });
77
+ await tp.write('ns', { upsert_rows: [{ id: 1, x: 'uno' }] });
78
+ const q = await tp.query('ns', { ...all, include_attributes: true });
79
+ return same(q.body.rows, [{ id: 1, x: 'uno' }]);
80
+ })),
81
+ done('turbopuffer.write.upsert_columns', 'write', 'write upsert_columns stores column-oriented documents and refuses ragged columns', 'common', () => withRoot(async (tp) => {
82
+ const w = await tp.write('ns', { upsert_columns: { id: ['a', 'b'], n: [1, 2] } });
83
+ const q = await tp.query('ns', { ...all, include_attributes: ['n'] });
84
+ const bad = await tp.write('ns', { upsert_columns: { id: ['c', 'd'], n: [1] } });
85
+ return w.body.rows_upserted === 2 && same(q.body.rows, [{ id: 'a', n: 1 }, { id: 'b', n: 2 }]) && isError(bad, 400);
86
+ })),
87
+ done('turbopuffer.write.patch_rows', 'write', 'patch_rows / patch_columns merge into existing documents and never create one', 'common', () => withRoot(async (tp) => {
88
+ await tp.write('ns', { upsert_rows: [{ id: 'a', x: 1, y: 2 }] });
89
+ const w = await tp.write('ns', { patch_rows: [{ id: 'a', y: 3, z: 'new' }, { id: 'ghost', y: 9 }] });
90
+ const c = await tp.write('ns', { patch_columns: { id: ['a'], x: [5] } });
91
+ const q = await tp.query('ns', { ...all, include_attributes: true });
92
+ return w.body.rows_patched === 1 && c.body.rows_patched === 1 && same(q.body.rows, [{ id: 'a', x: 5, y: 3, z: 'new' }]);
93
+ })),
94
+ done('turbopuffer.write.deletes', 'write', 'write deletes removes documents by id', 'core', () => withRoot(async (tp) => {
95
+ await tp.write('ns', PEOPLE);
96
+ const w = await tp.write('ns', { deletes: ['p1', 'p3'] });
97
+ const q = await tp.query('ns', all);
98
+ return w.status === 200 && w.body.rows_deleted === 2 && same(ids(q), ['p2']);
99
+ })),
100
+ done('turbopuffer.write.delete_by_filter', 'write', 'delete_by_filter removes exactly the documents the filter matches', 'common', () => withRoot(async (tp) => {
101
+ await tp.write('ns', PEOPLE);
102
+ const w = await tp.write('ns', { delete_by_filter: ['score', 'Gte', 7] });
103
+ const q = await tp.query('ns', all);
104
+ return w.body.rows_deleted === 2 && same(ids(q), ['p2']);
105
+ })),
106
+ done('turbopuffer.write.patch_by_filter', 'write', 'patch_by_filter patches exactly the documents the filter matches', 'common', () => withRoot(async (tp) => {
107
+ await tp.write('ns', PEOPLE);
108
+ const w = await tp.write('ns', { patch_by_filter: { filters: ['group', 'Eq', 'g1'], patch: { flagged: true } } });
109
+ const q = await tp.query('ns', { ...all, filters: ['flagged', 'Eq', true] });
110
+ return w.body.rows_patched === 1 && same(ids(q), ['p1']);
111
+ })),
112
+ done('turbopuffer.write.return_affected_ids', 'write', 'return_affected_ids lists upserted, patched and deleted ids', 'niche', () => withRoot(async (tp) => {
113
+ await tp.write('ns', { upsert_rows: [{ id: 'a' , v: 1 }] });
114
+ const w = await tp.write('ns', { upsert_rows: [{ id: 'b', v: 2 }], patch_rows: [{ id: 'a', v: 3 }], deletes: ['a'], return_affected_ids: true });
115
+ return same(w.body.upserted_ids, ['b']) && same(w.body.patched_ids, ['a']) && same(w.body.deleted_ids, ['a']) && w.body.rows_affected === 3;
116
+ })),
117
+ done('turbopuffer.write.schema_declared', 'write', 'a declared schema is kept; full-text attributes default to filterable:false', 'core', () => withRoot(async (tp) => {
118
+ await tp.write('ns', { ...PEOPLE, schema: { ...FTS, group: { type: 'string', filterable: true } } });
119
+ const s = await tp.req('GET', '/v1/namespaces/ns/schema');
120
+ return s.status === 200 && s.body.title.type === 'string' && s.body.title.filterable === false
121
+ && s.body.title.full_text_search.tokenizer === 'word_v2' && s.body.title.full_text_search.k1 === 1.2 && s.body.group.filterable === true
122
+ && s.body.score.type === 'int' && s.body.tags.type === '[]string';
123
+ })),
124
+ done('turbopuffer.write.schema_type_conflict', 'write', 'a value or schema of the wrong type is refused with a 400 and writes nothing', 'common', () => withRoot(async (tp) => {
125
+ await tp.write('ns', { upsert_rows: [{ id: 'a', n: 1 }] });
126
+ const bad = await tp.write('ns', { upsert_rows: [{ id: 'b', n: 'one' }] });
127
+ const badSchema = await tp.write('ns', { upsert_rows: [{ id: 'c', n: 2 }], schema: { n: 'string' } });
128
+ const q = await tp.query('ns', all);
129
+ return isError(bad, 400) && isError(badSchema, 400) && same(ids(q), ['a']);
130
+ })),
131
+ done('turbopuffer.write.invalid_requests', 'write', 'malformed writes (bad id, empty request, unknown field, bad namespace name) are refused with 400', 'common', () => withRoot(async (tp) => {
132
+ const a = await tp.write('ns', { upsert_rows: [{ id: -1 }] });
133
+ const b = await tp.write('ns', {});
134
+ const c = await tp.write('ns', { upsert_rowz: [] });
135
+ const d = await tp.write('bad%20name', { upsert_rows: [{ id: 'x' }] });
136
+ const q = await tp.query('ns', all);
137
+ return isError(a, 400) && isError(b, 400) && isError(c, 400) && isError(d, 400) && isError(q, 404);
138
+ })),
139
+ done('turbopuffer.write.vectors', 'write', 'vectors need a distance_metric and a consistent dimension', 'common', () => withRoot(async (tp) => {
140
+ const noMetric = await tp.write('ns', { upsert_rows: [{ id: 'a', vector: [1, 0] }] });
141
+ const okw = await tp.write('ns', { upsert_rows: [{ id: 'a', vector: [1, 0] }], distance_metric: 'cosine_distance' });
142
+ const wrongDims = await tp.write('ns', { upsert_rows: [{ id: 'b', vector: [1, 0, 0] }], distance_metric: 'cosine_distance' });
143
+ return isError(noMetric, 400) && okw.status === 200 && isError(wrongDims, 400);
144
+ })),
145
+ done('turbopuffer.write.dirty_state_recreate', 'write', 'a namespace deleted and re-written starts clean (no resurrected documents or schema)', 'common', () => withRoot(async (tp) => {
146
+ await tp.write('ns', { upsert_rows: [{ id: 'old', n: 1 }] });
147
+ await tp.req('DELETE', '/v2/namespaces/ns');
148
+ const w = await tp.write('ns', { upsert_rows: [{ id: 'new', n: 'now a string' }] });
149
+ const q = await tp.query('ns', { ...all, include_attributes: true });
150
+ return w.status === 200 && same(q.body.rows, [{ id: 'new', n: 'now a string' }]);
151
+ })),
152
+ todo('turbopuffer.write.conditional', 'write', 'conditional writes: upsert_condition / patch_condition / delete_condition with $ref_new', 'common'),
153
+ todo('turbopuffer.write.encryption', 'write', 'customer-managed encryption (encryption.mode customer-managed)', 'niche'),
154
+ todo('turbopuffer.write.sharding', 'write', 'sharding config on a namespace\'s inaugural write', 'niche'),
155
+ todo('turbopuffer.write.respond_async', 'write', 'Prefer: respond-async — a 202 with Location polling (the twin always answers synchronously, which the SDK accepts)', 'niche'),
156
+ todo('turbopuffer.write.backpressure', 'write', 'write backpressure (429 when unindexed data exceeds the limit) and disable_backpressure', 'niche'),
157
+
158
+ // ── namespaces ───────────────────────────────────────────────────────────────────────────
159
+ done('turbopuffer.namespaces.delete_all', 'namespaces', 'deleteAll removes the namespace; later reads and a second deleteAll answer 404', 'core', () => withRoot(async (tp) => {
160
+ await tp.write('ns', PEOPLE);
161
+ const d = await tp.req('DELETE', '/v2/namespaces/ns');
162
+ const q = await tp.query('ns', all);
163
+ const again = await tp.req('DELETE', '/v2/namespaces/ns');
164
+ return d.status === 200 && same(d.body, { status: 'OK' }) && isError(q, 404) && isError(again, 404);
165
+ })),
166
+ done('turbopuffer.namespaces.list', 'namespaces', 'GET /v1/namespaces lists namespaces by prefix with cursor paging', 'common', () => withRoot(async (tp) => {
167
+ for (const n of ['app-a', 'app-b', 'other']) await tp.write(n, { upsert_rows: [{ id: 1 }] });
168
+ const p1 = await tp.req('GET', '/v1/namespaces?prefix=app-&page_size=1');
169
+ const p2 = await tp.req('GET', `/v1/namespaces?prefix=app-&page_size=1&cursor=${p1.body.next_cursor}`);
170
+ return same(p1.body.namespaces, [{ id: 'app-a' }]) && p1.body.next_cursor === 'app-a' && same(p2.body.namespaces, [{ id: 'app-b' }]) && p2.body.next_cursor === undefined;
171
+ })),
172
+ done('turbopuffer.namespaces.metadata', 'namespaces', 'GET /v2/namespaces/{ns}/metadata reports row count, bytes, timestamps and schema', 'common', () => withRoot(async (tp) => {
173
+ await tp.write('ns', PEOPLE);
174
+ const m = await tp.req('GET', '/v2/namespaces/ns/metadata');
175
+ const missing = await tp.req('GET', '/v2/namespaces/nope/metadata');
176
+ return m.status === 200 && m.body.approx_row_count === 3 && m.body.approx_logical_bytes > 0 && m.body.created_at === AT
177
+ && same(m.body.index, { status: 'up-to-date' }) && m.body.schema.title.type === 'string' && isError(missing, 404);
178
+ })),
179
+ done('turbopuffer.namespaces.schema_get', 'namespaces', 'GET /v1/namespaces/{ns}/schema returns every attribute\'s resolved config', 'common', () => withRoot(async (tp) => {
180
+ await tp.write('ns', { upsert_rows: [{ id: 'a', f: 1.5, ok: true }] });
181
+ const s = await tp.req('GET', '/v1/namespaces/ns/schema');
182
+ return s.status === 200 && s.body.f.type === 'float' && s.body.ok.type === 'bool' && s.body.f.filterable === true;
183
+ })),
184
+ done('turbopuffer.namespaces.schema_update', 'namespaces', 'POST /v1/namespaces/{ns}/schema updates configs and refuses a type change', 'niche', () => withRoot(async (tp) => {
185
+ await tp.write('ns', { upsert_rows: [{ id: 'a', note: 'hello world' }] });
186
+ const u = await tp.req('POST', '/v1/namespaces/ns/schema', { note: { type: 'string', full_text_search: { tokenizer: 'word_v2' }, filterable: true } });
187
+ const bad = await tp.req('POST', '/v1/namespaces/ns/schema', { note: 'int' });
188
+ const q = await tp.query('ns', { rank_by: ['note', 'BM25', 'world'] });
189
+ return u.status === 200 && u.body.note.full_text_search.tokenizer === 'word_v2' && isError(bad, 400) && same(ids(q), ['a']);
190
+ })),
191
+ done('turbopuffer.namespaces.hint_cache_warm', 'namespaces', 'GET /v1/namespaces/{ns}/hint_cache_warm is accepted for an existing namespace', 'niche', () => withRoot(async (tp) => {
192
+ await tp.write('ns', { upsert_rows: [{ id: 1 }] });
193
+ const h = await tp.req('GET', '/v1/namespaces/ns/hint_cache_warm');
194
+ const missing = await tp.req('GET', '/v1/namespaces/nope/hint_cache_warm');
195
+ return h.status === 202 && h.body.status === 'ACCEPTED' && isError(missing, 404);
196
+ })),
197
+ todo('turbopuffer.namespaces.copy_from', 'namespaces', 'copyFrom / copy_from_namespace (incl. cross-region with source_api_key)', 'niche'),
198
+ todo('turbopuffer.namespaces.branch_from', 'namespaces', 'branchFrom / branch_from_namespace copy-on-write clones', 'niche'),
199
+ todo('turbopuffer.namespaces.update_metadata', 'namespaces', 'PATCH /v1/namespaces/{ns}/metadata (pinning replicas)', 'niche'),
200
+
201
+ // ── query ────────────────────────────────────────────────────────────────────────────────
202
+ done('turbopuffer.query.missing_namespace', 'query', 'querying a namespace that does not exist answers 404', 'core', () => withRoot(async (tp) => {
203
+ const q = await tp.query('nope', all);
204
+ return isError(q, 404) && q.body.error.includes('nope');
205
+ })),
206
+ done('turbopuffer.query.bm25', 'query', 'rank_by BM25 ranks matching documents by score and omits non-matching ones', 'core', () => withRoot(async (tp) => {
207
+ await tp.write('ns', PEOPLE);
208
+ const q = await tp.query('ns', { rank_by: ['title', 'BM25', 'dub'], top_k: 10 });
209
+ const rows = q.body.rows;
210
+ return same(ids(q), ['p3', 'p1']) && rows[0].$dist > rows[1].$dist && rows[1].$dist > 0;
211
+ })),
212
+ done('turbopuffer.query.bm25_last_as_prefix', 'query', 'BM25 last_as_prefix matches a typed prefix; a lone prefix token scores every match 1', 'common', () => withRoot(async (tp) => {
213
+ await tp.write('ns', PEOPLE);
214
+ const q = await tp.query('ns', { rank_by: ['title', 'BM25', 'ste', { last_as_prefix: true }] });
215
+ const exact = await tp.query('ns', { rank_by: ['title', 'BM25', 'ste'] });
216
+ return same(ids(q), ['p1', 'p2']) && q.body.rows.every((r: any) => r.$dist === 1) && same(exact.body.rows, []);
217
+ })),
218
+ done('turbopuffer.query.bm25_combinators', 'query', 'rank_by Sum / Max / Product over BM25 clauses', 'niche', () => withRoot(async (tp) => {
219
+ await tp.write('ns', { upsert_rows: [{ id: 'a', t: 'red', u: 'blue' }, { id: 'b', t: 'red', u: 'green' }], schema: { t: { type: 'string', full_text_search: { tokenizer: 'word_v2' } }, u: { type: 'string', full_text_search: { tokenizer: 'word_v2' } } } });
220
+ const sum = await tp.query('ns', { rank_by: ['Sum', [['t', 'BM25', 'red'], ['u', 'BM25', 'blue']]] });
221
+ const prod = await tp.query('ns', { rank_by: ['Product', 2, ['u', 'BM25', 'green']] });
222
+ const one = await tp.query('ns', { rank_by: ['u', 'BM25', 'green'] });
223
+ return same(ids(sum), ['a', 'b']) && sum.body.rows[0].$dist > sum.body.rows[1].$dist && Math.abs(prod.body.rows[0].$dist - 2 * one.body.rows[0].$dist) < 1e-9;
224
+ })),
225
+ done('turbopuffer.query.rank_by_attribute', 'query', 'rank_by [attr, asc|desc] orders by attribute, absent values last', 'core', () => withRoot(async (tp) => {
226
+ await tp.write('ns', { upsert_rows: [{ id: 'a', n: 2 }, { id: 'b', n: 9 }, { id: 'c' }, { id: 'd', n: 5 }] });
227
+ const desc = await tp.query('ns', { rank_by: ['n', 'desc'] });
228
+ const byId = await tp.query('ns', { rank_by: ['id', 'desc'], top_k: 2 });
229
+ return same(ids(desc), ['b', 'd', 'a', 'c']) && same(ids(byId), ['d', 'c']) && desc.body.rows[0].$dist === undefined;
230
+ })),
231
+ done('turbopuffer.query.ann', 'query', 'rank_by [vector, ANN, q] ranks by exact cosine / euclidean-squared distance', 'core', () => withRoot(async (tp) => {
232
+ await tp.write('ns', { upsert_rows: [{ id: 'x', vector: [1, 0] }, { id: 'y', vector: [0, 1] }, { id: 'z', vector: [1, 1] }], distance_metric: 'cosine_distance' });
233
+ const q = await tp.query('ns', { rank_by: ['vector', 'ANN', [1, 0]], top_k: 3, include_attributes: ['vector'] });
234
+ const d = q.body.rows.map((r: any) => r.$dist);
235
+ const badDims = await tp.query('ns', { rank_by: ['vector', 'ANN', [1, 0, 0]] });
236
+ return same(ids(q), ['x', 'z', 'y']) && Math.abs(d[0]) < 1e-12 && Math.abs(d[1] - (1 - Math.SQRT1_2)) < 1e-9 && Math.abs(d[2] - 1) < 1e-12
237
+ && same(q.body.rows[0].vector, [1, 0]) && isError(badDims, 400);
238
+ })),
239
+ done('turbopuffer.query.top_k', 'query', 'top_k / limit truncate the ranked rows (default 10)', 'core', () => withRoot(async (tp) => {
240
+ await tp.write('ns', { upsert_rows: Array.from({ length: 12 }, (_, i) => ({ id: i })) });
241
+ const def = await tp.query('ns', { rank_by: ['id', 'asc'] });
242
+ const two = await tp.query('ns', { rank_by: ['id', 'asc'], limit: 2 });
243
+ const bad = await tp.query('ns', { rank_by: ['id', 'asc'], top_k: 0 });
244
+ return def.body.rows.length === 10 && same(ids(two), [0, 1]) && isError(bad, 400);
245
+ })),
246
+ done('turbopuffer.query.include_attributes', 'query', 'include_attributes false|true|[names] and exclude_attributes shape the returned rows', 'core', () => withRoot(async (tp) => {
247
+ await tp.write('ns', { upsert_rows: [{ id: 'a', x: 1, y: 2 }] });
248
+ const none = await tp.query('ns', { ...all, include_attributes: false });
249
+ const some = await tp.query('ns', { ...all, include_attributes: ['y'] });
250
+ const allAttrs = await tp.query('ns', { ...all, include_attributes: true });
251
+ const ex = await tp.query('ns', { ...all, exclude_attributes: ['x'] });
252
+ return same(none.body.rows, [{ id: 'a' }]) && same(some.body.rows, [{ id: 'a', y: 2 }]) && same(allAttrs.body.rows, [{ id: 'a', x: 1, y: 2 }]) && same(ex.body.rows, [{ id: 'a', y: 2 }]);
253
+ })),
254
+ done('turbopuffer.query.aggregate_by', 'query', 'aggregate_by Count / Count(attr) / Sum(attr) over the filtered documents', 'common', () => withRoot(async (tp) => {
255
+ await tp.write('ns', PEOPLE);
256
+ const q = await tp.query('ns', { filters: ['score', 'Gt', 5], aggregate_by: { total: ['Count'], grouped: ['Count', 'group'], points: ['Sum', 'score'] } });
257
+ return q.status === 200 && same(q.body.aggregations, { total: 2, grouped: 1, points: 17 }) && q.body.rows === undefined && typeof q.body.billing === 'object';
258
+ })),
259
+ done('turbopuffer.query.multi_query', 'query', 'multiQuery answers each sub-query independently, in order', 'common', () => withRoot(async (tp) => {
260
+ await tp.write('ns', PEOPLE);
261
+ const q = await tp.req('POST', '/v2/namespaces/ns/query?stainless_overload=multiQuery', { queries: [{ rank_by: ['title', 'BM25', 'writer'], include_attributes: false }, { filters: ['group', 'Eq', 'g1'], aggregate_by: { n: ['Count'] } }] });
262
+ return q.status === 200 && same(q.body.results, [{ rows: [{ id: 'p2', $dist: q.body.results?.[0]?.rows?.[0]?.$dist }] }, { aggregations: { n: 1 } }]) && q.body.results[0].rows[0].$dist > 0;
263
+ })),
264
+ done('turbopuffer.query.rank_by_required', 'query', 'a query with neither rank_by nor aggregate_by is refused with 400', 'common', () => withRoot(async (tp) => {
265
+ await tp.write('ns', PEOPLE);
266
+ return isError(await tp.query('ns', { top_k: 3 }), 400);
267
+ })),
268
+ done('turbopuffer.query.response_envelope', 'query', 'query responses carry billing and performance objects', 'common', () => withRoot(async (tp) => {
269
+ await tp.write('ns', PEOPLE);
270
+ const q = await tp.query('ns', all);
271
+ return q.body.billing.billable_logical_bytes_queried > 0 && q.body.billing.billable_logical_bytes_returned > 0 && q.body.performance.approx_namespace_size === 3;
272
+ })),
273
+ todo('turbopuffer.query.group_by', 'query', 'group_by with aggregation_groups', 'common'),
274
+ todo('turbopuffer.query.compute_attributes', 'query', 'compute_attributes expressions ($ref_new, Highlight, VectorDist, Embed)', 'niche'),
275
+ todo('turbopuffer.query.aggregate_with_rank', 'query', 'aggregate_by together with rank_by in one query', 'niche'),
276
+ todo('turbopuffer.query.limit_per', 'query', 'limit.per (per-attribute diversity limits)', 'niche'),
277
+ todo('turbopuffer.query.multi_rerank', 'query', 'multiQuery rerank_by RRF fusion on the server', 'niche'),
278
+ todo('turbopuffer.query.rank_expressions', 'query', 'rank_by expressions beyond BM25/Sum/Max/Product: Saturate, Decay, Dist, Attribute, filters as rank terms', 'niche'),
279
+ todo('turbopuffer.query.highlight', 'query', 'Highlight expressions and highlight config (fragments, offsets)', 'niche'),
280
+ todo('turbopuffer.query.explain', 'query', 'POST /v2/namespaces/{ns}/explain_query', 'niche'),
281
+ todo('turbopuffer.query.recall', 'query', 'POST /v1/namespaces/{ns}/_debug/recall', 'niche'),
282
+
283
+ // ── filters ──────────────────────────────────────────────────────────────────────────────
284
+ done('turbopuffer.filters.eq_in', 'filters', 'Eq / NotEq / In / NotIn, where an absent attribute matches NotIn and NotEq', 'core', () => withRoot(async (tp) => {
285
+ await tp.write('ns', PEOPLE);
286
+ const eq = await tp.query('ns', { ...all, filters: ['group', 'Eq', 'g1'] });
287
+ const inq = await tp.query('ns', { ...all, filters: ['group', 'In', ['g1', 'g2']] });
288
+ const notIn = await tp.query('ns', { ...all, filters: ['group', 'NotIn', ['g1']] });
289
+ const notEq = await tp.query('ns', { ...all, filters: ['group', 'NotEq', 'g2'] });
290
+ const idIn = await tp.query('ns', { ...all, filters: ['id', 'In', ['p3', 'p1']] });
291
+ return same(ids(eq), ['p1']) && same(ids(inq), ['p1', 'p2']) && same(ids(notIn), ['p2', 'p3']) && same(ids(notEq), ['p1', 'p3']) && same(ids(idIn), ['p1', 'p3']);
292
+ })),
293
+ done('turbopuffer.filters.array_ops', 'filters', 'Contains / NotContains / ContainsAny / NotContainsAny on []string attributes', 'common', () => withRoot(async (tp) => {
294
+ await tp.write('ns', PEOPLE);
295
+ const any = await tp.query('ns', { ...all, filters: ['tags', 'ContainsAny', ['a', 'z']] });
296
+ const notAny = await tp.query('ns', { ...all, filters: ['tags', 'NotContainsAny', ['a']] });
297
+ const has = await tp.query('ns', { ...all, filters: ['tags', 'Contains', 'b'] });
298
+ return same(ids(any), ['p1']) && same(ids(notAny), ['p2', 'p3']) && same(ids(has), ['p1', 'p2']);
299
+ })),
300
+ done('turbopuffer.filters.range', 'filters', 'Lt / Lte / Gt / Gte and the Any* array forms', 'common', () => withRoot(async (tp) => {
301
+ await tp.write('ns', { upsert_rows: [{ id: 'a', n: 1, ns: [1, 9] }, { id: 'b', n: 5, ns: [2] }, { id: 'c', n: 9, ns: [] }] });
302
+ const lt = await tp.query('ns', { ...all, filters: ['n', 'Lt', 5] });
303
+ const gte = await tp.query('ns', { ...all, filters: ['n', 'Gte', 5] });
304
+ const anyGt = await tp.query('ns', { ...all, filters: ['ns', 'AnyGt', 5] });
305
+ return same(ids(lt), ['a']) && same(ids(gte), ['b', 'c']) && same(ids(anyGt), ['a']);
306
+ })),
307
+ done('turbopuffer.filters.boolean', 'filters', 'And / Or / Not compose filters', 'core', () => withRoot(async (tp) => {
308
+ await tp.write('ns', PEOPLE);
309
+ const q = await tp.query('ns', { ...all, filters: ['And', [['score', 'Gte', 5], ['Or', [['group', 'Eq', 'g1'], ['Not', ['tags', 'Contains', 'b']]]]]] });
310
+ return same(ids(q), ['p1', 'p3']);
311
+ })),
312
+ done('turbopuffer.filters.glob', 'filters', 'Glob / NotGlob / IGlob pattern filters', 'niche', () => withRoot(async (tp) => {
313
+ await tp.write('ns', { upsert_rows: [{ id: 'a', path: 'src/app.ts' }, { id: 'b', path: 'SRC/lib.ts' }, { id: 'c', path: 'docs/a.md' }] });
314
+ const g = await tp.query('ns', { ...all, filters: ['path', 'Glob', 'src/*.ts'] });
315
+ const ig = await tp.query('ns', { ...all, filters: ['path', 'IGlob', 'src/*'] });
316
+ const ng = await tp.query('ns', { ...all, filters: ['path', 'NotGlob', '*.ts'] });
317
+ return same(ids(g), ['a']) && same(ids(ig), ['a', 'b']) && same(ids(ng), ['c']);
318
+ })),
319
+ done('turbopuffer.filters.not_filterable', 'filters', 'a comparison filter on a non-filterable attribute is refused with 400', 'common', () => withRoot(async (tp) => {
320
+ await tp.write('ns', PEOPLE);
321
+ const q = await tp.query('ns', { ...all, filters: ['title', 'Eq', 'x'] });
322
+ const bad = await tp.query('ns', { ...all, filters: ['group', 'Between', [1, 2]] });
323
+ return isError(q, 400) && isError(bad, 400);
324
+ })),
325
+ done('turbopuffer.filters.contains_all_tokens', 'filters', 'ContainsAllTokens (with last_as_prefix) reads the full-text index', 'common', () => withRoot(async (tp) => {
326
+ await tp.write('ns', PEOPLE);
327
+ const q = await tp.query('ns', { ...all, filters: ['title', 'ContainsAllTokens', 'dub fo', { last_as_prefix: true }] });
328
+ const exact = await tp.query('ns', { ...all, filters: ['title', 'ContainsAllTokens', 'dub fo'] });
329
+ const noFts = await tp.query('ns', { ...all, filters: ['group', 'ContainsAllTokens', 'g1'] });
330
+ return same(ids(q), ['p1']) && same(ids(exact), []) && isError(noFts, 400);
331
+ })),
332
+ done('turbopuffer.filters.contains_any_token', 'filters', 'ContainsAnyToken matches documents sharing any query token', 'common', () => withRoot(async (tp) => {
333
+ await tp.write('ns', PEOPLE);
334
+ const q = await tp.query('ns', { ...all, filters: ['title', 'ContainsAnyToken', 'writer engineer'] });
335
+ const stop = await tp.query('ns', { ...all, filters: ['title', 'ContainsAllTokens', 'the'] });
336
+ return same(ids(q), ['p2', 'p3']) && same(ids(stop), []);
337
+ })),
338
+ done('turbopuffer.filters.id_paging', 'filters', 'id range filters page mixed numeric and string ids in rank_by [id, asc] order without skipping either type', 'common', () => withRoot(async (tp) => {
339
+ await tp.write('ns', { upsert_rows: [{ id: 'b' }, { id: 2 }, { id: 'a' }, { id: 10 }] });
340
+ const first = await tp.query('ns', { rank_by: ['id', 'asc'], top_k: 2 });
341
+ const next = await tp.query('ns', { rank_by: ['id', 'asc'], top_k: 2, filters: ['id', 'Gt', 10] });
342
+ return same(ids(first), [2, 10]) && same(ids(next), ['a', 'b']);
343
+ })),
344
+ todo('turbopuffer.filters.regex', 'filters', 'Regex filters (regex: true attributes)', 'common'),
345
+ todo('turbopuffer.filters.fuzzy', 'filters', 'Fuzzy filters (edit-distance matching)', 'niche'),
346
+ todo('turbopuffer.filters.contains_token_sequence', 'filters', 'ContainsTokenSequence (phrase) filters', 'niche'),
347
+
348
+ // ── full-text search configuration ───────────────────────────────────────────────────────
349
+ done('turbopuffer.fts.word_v2', 'fts', 'word_v2 splits URLs and punctuation into word tokens, case-insensitively; stopwords are kept unless remove_stopwords is set', 'core', () => withRoot(async (tp) => {
350
+ await tp.write('ns', { upsert_rows: [{ id: 'u', title: 'https://www.ScottDigital-42.techcorp.io' }, { id: 's', title: 'the and of' }], schema: FTS });
351
+ await tp.write('stop', { upsert_rows: [{ id: 's', title: 'the and of' }], schema: { title: { type: 'string', full_text_search: { tokenizer: 'word_v2', remove_stopwords: true } } } });
352
+ const q = await tp.query('ns', { rank_by: ['title', 'BM25', 'scottdigital'] });
353
+ const kept = await tp.query('ns', { rank_by: ['title', 'BM25', 'the'] });
354
+ const stop = await tp.query('stop', { rank_by: ['title', 'BM25', 'the'] });
355
+ return same(ids(q), ['u']) && same(ids(kept), ['s']) && same(stop.body.rows, []);
356
+ })),
357
+ done('turbopuffer.fts.unmodelled_config_refused', 'fts', 'unmodelled tokenizers and languages are refused, never silently approximated', 'common', () => withRoot(async (tp) => {
358
+ const v4 = await tp.write('ns', { upsert_rows: [{ id: 'a', t: 'x' }], schema: { t: { type: 'string', full_text_search: { tokenizer: 'word_v0' } } } });
359
+ const stem = await tp.write('ns', { upsert_rows: [{ id: 'a', t: 'x' }], schema: { t: { type: 'string', full_text_search: { tokenizer: 'word_v2', language: 'german' } } } });
360
+ return isError(v4, 400) && v4.body.error.includes('turbopuffer.fts.tokenizers') && isError(stem, 400);
361
+ })),
362
+ todo('turbopuffer.fts.tokenizers', 'fts', 'tokenizers word_v0 / word_v1 / word_v3 / word_v4 (the default)', 'common'),
363
+ todo('turbopuffer.fts.stemming', 'fts', 'language-specific stemming', 'common'),
364
+ todo('turbopuffer.fts.languages', 'fts', 'non-English languages (stopword lists, stemmers)', 'niche'),
365
+ todo('turbopuffer.fts.exact_bm25_scores', 'fts', 'BM25 $dist values byte-equal to Turbopuffer\'s own scorer (the twin\'s are deterministic textbook Okapi)', 'common'),
366
+
367
+ // ── vectors / schema options ─────────────────────────────────────────────────────────────
368
+ todo('turbopuffer.vectors.base64', 'vectors', 'base64 vector encoding (writes and vector_encoding: base64)', 'niche'),
369
+ todo('turbopuffer.vectors.named_attributes', 'vectors', 'vector-typed attributes other than "vector"', 'niche'),
370
+ todo('turbopuffer.vectors.multi', 'vectors', 'multi-vector ANN / late interaction', 'niche'),
371
+ todo('turbopuffer.vectors.sparse', 'vectors', 'sparse vectors ({}f16) and SparseKNN', 'niche'),
372
+ todo('turbopuffer.vectors.embed', 'vectors', 'server-side embedding (Embed expressions, embed schema)', 'common'),
373
+ todo('turbopuffer.schema.embed', 'schema', 'the embed schema option', 'niche'),
374
+ todo('turbopuffer.schema.fuzzy', 'schema', 'the fuzzy schema option', 'niche'),
375
+ todo('turbopuffer.schema.sparse_knn', 'schema', 'the sparse_knn schema option', 'niche'),
376
+
377
+ // ── protocol ─────────────────────────────────────────────────────────────────────────────
378
+ done('turbopuffer.auth.bearer', 'auth', 'requests without a Bearer API key are refused with 401', 'core', () => withRoot(async (tp) => {
379
+ const none = await tp.req('GET', '/v1/namespaces', undefined, {});
380
+ const empty = await tp.req('GET', '/v1/namespaces', undefined, { authorization: 'Bearer ' });
381
+ const okr = await tp.req('GET', '/v1/namespaces');
382
+ return isError(none, 401) && isError(empty, 401) && okr.status === 200;
383
+ })),
384
+ done('turbopuffer.wire.region_prefix', 'wire', 'a leading region segment (TURBOPUFFER_BASE_URL=<twin>/{region}) reaches the same namespace', 'core', () => withRoot(async (tp) => {
385
+ await tp.req('POST', '/aws-us-east-1/v2/namespaces/ns', { upsert_rows: [{ id: 'r' }] });
386
+ const q = await tp.req('POST', '/v2/namespaces/ns/query', all);
387
+ const q2 = await tp.req('POST', '//gcp-us-central1/v2/namespaces/ns/query', all);
388
+ return same(ids(q), ['r']) && same(ids(q2), ['r']);
389
+ })),
390
+ done('turbopuffer.errors.envelope', 'errors', 'errors wear {status:"error", error}; unknown routes 404; unmodelled routes name their gap', 'common', () => withRoot(async (tp) => {
391
+ const unknown = await tp.req('GET', '/v9/whatever');
392
+ const gap = await tp.req('POST', '/v2/namespaces/ns/explain_query', {});
393
+ const bad = await handleTurbopufferTwinRequest({ method: 'POST', path: '/v2/namespaces/ns', headers: AUTH, root: tp.root, occurredAt: AT, body: '{not json' });
394
+ return isError(unknown, 404) && isError(gap, 404) && gap.body.error.includes('turbopuffer.query.explain') && bad.status === 400;
395
+ })),
396
+ todo('turbopuffer.errors.vendor_error_strings', 'errors', 'Turbopuffer\'s literal error message strings (the twin\'s are its own)', 'common'),
397
+ todo('turbopuffer.legacy.v1_api', 'legacy', 'the v1 namespace API older SDKs call (POST /v1/namespaces/{ns}, /v1/namespaces/{ns}/query)', 'niche'),
398
+ done('turbopuffer.conformance.endpoint_probe', 'conformance', 'one live request per claimed endpoint asserts its outcome; the router census holds', 'common', async () => (await checkTurbopufferConformance()).ok),
399
+
400
+ // ── connector (the real state system) ────────────────────────────────────────────────────
401
+ done('turbopuffer.connector.perform_write', 'connector', 'perform replays a recorded write / deleteAll / schema update against the vendor wire', 'core', async () => {
402
+ const seen: Array<{ method: string; path: string; body?: string }> = [];
403
+ const execute: RemoteExecute = async (r) => { seen.push({ method: r.method, path: r.path, ...(typeof r.body === 'string' ? { body: r.body } : {}) }); return { status: 200, headers: {}, body: '{"status":"OK","rows_affected":1}' }; };
404
+ const base = { id: 'x', service: 'turbopuffer', op: 'set', subject: { type: 'namespace', id: 'my-ns' }, occurredAt: AT } as unknown as TwinAction;
405
+ const input = { upsert_rows: [{ id: 'a', t: 'x' }] };
406
+ const w = await performTurbopufferAction(execute, { ...base, operation: 'namespace.write', input } as TwinAction, { resolve: (_t, id) => id });
407
+ await performTurbopufferAction(execute, { ...base, operation: 'namespace.delete_all' } as TwinAction, { resolve: (_t, id) => id });
408
+ let refused = false;
409
+ try { await performTurbopufferAction(execute, { ...base, operation: 'namespace.bogus' } as TwinAction, { resolve: (_t, id) => id }); } catch { refused = true; }
410
+ return w.externalId === 'my-ns' && seen[0]?.method === 'POST' && seen[0]?.path === '/v2/namespaces/my-ns' && same(JSON.parse(seen[0]?.body ?? 'null'), input)
411
+ && seen[1]?.method === 'DELETE' && seen[1]?.path === '/v2/namespaces/my-ns' && refused;
412
+ }, 'connector'),
413
+ done('turbopuffer.connector.refresh', 'connector', 'refresh observes a real account\'s namespaces, schema and documents into the tree', 'common', async () => {
414
+ const vendor = mkdtempSync(join(tmpdir(), 'turbopuffer-vendor-'));
415
+ const local = mkdtempSync(join(tmpdir(), 'turbopuffer-local-'));
416
+ try {
417
+ const send = async (root: string, method: string, path: string, body?: string) => {
418
+ const r = await handleTurbopufferTwinRequest({ method, path, headers: AUTH, root, occurredAt: AT, ...(body === undefined ? {} : { body }) });
419
+ return { status: r.status, headers: {}, body: JSON.stringify(r.body) };
420
+ };
421
+ await send(vendor, 'POST', '/v2/namespaces/remote', JSON.stringify(PEOPLE));
422
+ const execute: RemoteExecute = (r) => send(vendor, r.method, r.path, typeof r.body === 'string' ? r.body : undefined);
423
+ const report = await syncTurbopufferFromRemote(execute, { root: local, occurredAt: AT });
424
+ const q = JSON.parse((await send(local, 'POST', '/v2/namespaces/remote/query', JSON.stringify({ rank_by: ['title', 'BM25', 'writer'], include_attributes: ['group'] }))).body);
425
+ let threw = false;
426
+ try { await syncTurbopufferFromRemote(async () => ({ status: 401, headers: {}, body: '{"status":"error","error":"unauthorized"}' }), { root: local }); } catch { threw = true; }
427
+ return report.observed === 4 && same(q.rows?.map((r: any) => [r.id, r.group]), [['p2', 'g2']]) && threw;
428
+ } finally {
429
+ rmSync(vendor, { recursive: true, force: true });
430
+ rmSync(local, { recursive: true, force: true });
431
+ }
432
+ }, 'connector'),
433
+ ];
434
+
435
+ /** Committed area census: the SDK's surface groups (write, query, filters, full-text, vectors,
436
+ * schema options, namespace management) plus the protocol and connector areas. */
437
+ export const TURBOPUFFER_AREAS = [
438
+ 'auth',
439
+ 'conformance',
440
+ 'connector',
441
+ 'errors',
442
+ 'filters',
443
+ 'fts',
444
+ 'legacy',
445
+ 'namespaces',
446
+ 'query',
447
+ 'schema',
448
+ 'vectors',
449
+ 'wire',
450
+ 'write',
451
+ ] as const;
452
+
453
+ export function turbopufferCapabilities(): Promise<CapabilityReport> {
454
+ return checkCapabilities('turbopuffer', TURBOPUFFER_CAPABILITIES);
455
+ }
@@ -0,0 +1,101 @@
1
+ // Turbopuffer conformance (dev-only; lazily imported by the CLI). One real request per claimed
2
+ // endpoint against a throwaway root, each asserting an OUTCOME only the live handler produces
3
+ // (values, not statuses), plus a ROUTER_SURFACE pass: every route the router branches on is named
4
+ // and exercised, and an unclaimed route must answer the not-found envelope.
5
+ import { mkdtempSync, rmSync } from 'node:fs';
6
+ import { tmpdir } from 'node:os';
7
+ import { join } from 'node:path';
8
+ import { handleTurbopufferTwinRequest, turbopufferTwinSnapshot } from './turbopuffer-twin.ts';
9
+
10
+ export type TurbopufferConformanceReport = { ok: boolean; checksRun: number; failures: string[] };
11
+
12
+ const AUTH = { authorization: 'Bearer conformance' };
13
+ const AT = '2026-09-23T00:00:00.000Z';
14
+
15
+ type Probe = { endpoint: string; method: string; path: string; body?: unknown; expect: (status: number, body: any) => boolean };
16
+
17
+ const PROBES: Probe[] = [
18
+ {
19
+ endpoint: 'POST /v2/namespaces/{ns}', method: 'POST', path: '/v2/namespaces/conf',
20
+ body: { upsert_rows: [{ id: 'a', title: 'alpha beta' }, { id: 'b', title: 'beta gamma', tag: 'x' }], schema: { title: { type: 'string', full_text_search: { tokenizer: 'word_v2' } } } },
21
+ expect: (s, b) => s === 200 && b.status === 'OK' && b.rows_affected === 2 && b.rows_upserted === 2,
22
+ },
23
+ {
24
+ endpoint: 'POST /v2/namespaces/{ns}/query', method: 'POST', path: '/v2/namespaces/conf/query',
25
+ body: { rank_by: ['title', 'BM25', 'gamma'], top_k: 5, include_attributes: ['title'] },
26
+ expect: (s, b) => s === 200 && Array.isArray(b.rows) && b.rows.length === 1 && b.rows[0].id === 'b' && b.rows[0].title === 'beta gamma' && typeof b.rows[0].$dist === 'number',
27
+ },
28
+ {
29
+ endpoint: 'GET /v2/namespaces/{ns}/metadata', method: 'GET', path: '/v2/namespaces/conf/metadata',
30
+ expect: (s, b) => s === 200 && b.approx_row_count === 2 && b.schema?.title?.full_text_search?.tokenizer === 'word_v2',
31
+ },
32
+ {
33
+ endpoint: 'GET /v1/namespaces/{ns}/schema', method: 'GET', path: '/v1/namespaces/conf/schema',
34
+ expect: (s, b) => s === 200 && b.tag?.type === 'string' && b.title?.filterable === false,
35
+ },
36
+ {
37
+ endpoint: 'POST /v1/namespaces/{ns}/schema', method: 'POST', path: '/v1/namespaces/conf/schema',
38
+ body: { tag: { type: 'string', filterable: true } },
39
+ expect: (s, b) => s === 200 && b.tag?.filterable === true,
40
+ },
41
+ {
42
+ endpoint: 'GET /v1/namespaces/{ns}/hint_cache_warm', method: 'GET', path: '/v1/namespaces/conf/hint_cache_warm',
43
+ expect: (s, b) => s === 202 && b.status === 'ACCEPTED',
44
+ },
45
+ {
46
+ endpoint: 'GET /v1/namespaces', method: 'GET', path: '/v1/namespaces?prefix=con',
47
+ expect: (s, b) => s === 200 && Array.isArray(b.namespaces) && b.namespaces.length === 1 && b.namespaces[0].id === 'conf',
48
+ },
49
+ {
50
+ endpoint: 'DELETE /v2/namespaces/{ns}', method: 'DELETE', path: '/v2/namespaces/conf',
51
+ expect: (s, b) => s === 200 && b.status === 'OK',
52
+ },
53
+ ];
54
+
55
+ /** Every method+path pair the router branches on; each must be reached by a live request below. */
56
+ const ROUTER_SURFACE: Array<{ method: string; path: string; claimed: boolean }> = [
57
+ { method: 'GET', path: '/v1/namespaces', claimed: true },
58
+ { method: 'POST', path: '/v2/namespaces/x', claimed: true },
59
+ { method: 'DELETE', path: '/v2/namespaces/x', claimed: true },
60
+ { method: 'POST', path: '/v2/namespaces/x/query', claimed: true },
61
+ { method: 'GET', path: '/v2/namespaces/x/metadata', claimed: true },
62
+ { method: 'GET', path: '/v1/namespaces/x/schema', claimed: true },
63
+ { method: 'POST', path: '/v1/namespaces/x/schema', claimed: true },
64
+ { method: 'GET', path: '/v1/namespaces/x/hint_cache_warm', claimed: true },
65
+ { method: 'PATCH', path: '/v1/namespaces/x/metadata', claimed: false },
66
+ { method: 'POST', path: '/v2/namespaces/x/explain_query', claimed: false },
67
+ ];
68
+
69
+ export async function checkTurbopufferConformance(options: { root?: string } = {}): Promise<TurbopufferConformanceReport> {
70
+ const failures: string[] = [];
71
+ let checksRun = 0;
72
+ const root = options.root ?? mkdtempSync(join(tmpdir(), 'turbopuffer-conformance-'));
73
+ const send = (method: string, path: string, body?: unknown) =>
74
+ handleTurbopufferTwinRequest({ method, path, headers: AUTH, root, occurredAt: AT, ...(body === undefined ? {} : { body: JSON.stringify(body) }) });
75
+ try {
76
+ // Two-way bijection between the probe table and the snapshot's claimed endpoints.
77
+ const claimed = new Set(turbopufferTwinSnapshot().implementedEndpoints);
78
+ const probed = new Set(PROBES.map((p) => p.endpoint));
79
+ for (const e of claimed) if (!probed.has(e)) failures.push(`claimed endpoint without a probe: ${e}`);
80
+ for (const e of probed) if (!claimed.has(e)) failures.push(`probe for an unclaimed endpoint: ${e}`);
81
+ for (const p of PROBES) {
82
+ checksRun++;
83
+ const res = await send(p.method, p.path, p.body);
84
+ if (!p.expect(res.status, res.body)) failures.push(`${p.endpoint}: unexpected ${res.status} ${JSON.stringify(res.body).slice(0, 200)}`);
85
+ }
86
+ // Router census: an unclaimed route answers a 404 naming its gap; a claimed one is not the router's miss.
87
+ for (const r of ROUTER_SURFACE) {
88
+ checksRun++;
89
+ const res = await send(r.method, r.path, r.method === 'GET' || r.method === 'DELETE' ? undefined : {});
90
+ const miss = res.status === 404 && JSON.stringify(res.body).includes('not found: ');
91
+ if (r.claimed && miss) failures.push(`router: ${r.method} ${r.path} is claimed but answered the router miss`);
92
+ if (!r.claimed && !(res.status === 404 && JSON.stringify(res.body).includes('filed gap'))) failures.push(`router: ${r.method} ${r.path} should answer the filed-gap 404`);
93
+ }
94
+ checksRun++;
95
+ const unknown = await send('GET', '/v9/never');
96
+ if (!(unknown.status === 404 && (unknown.body as { status?: string }).status === 'error')) failures.push('an unknown route must answer the error envelope with 404');
97
+ } finally {
98
+ if (options.root === undefined) rmSync(root, { recursive: true, force: true });
99
+ }
100
+ return { ok: failures.length === 0, checksRun, failures };
101
+ }