@volter/twin-pinecone 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.
@@ -0,0 +1,430 @@
1
+ // Pinecone capability manifest — the EXPECTED REAL-PRODUCT SURFACE (the target), authored
2
+ // top-down from the real Pinecone vector-database API — grounded against the installed
3
+ // `@pinecone-database/pinecone` SDK's own generated TypeScript-fetch client (fetched read-only
4
+ // via `npm pack` during this build — see spec-sources.json) for every route/field-name/status,
5
+ // plus a handful of docs.pinecone.io reads for items the generated client doesn't settle
6
+ // (annotated doc-UNVERIFIED inline where the docs didn't resolve them either). This is the
7
+ // honest denominator: most entries start as `todo` and coverage reads LOW until the twin truly
8
+ // reaches 100% of the real API. `verify()` (required to count as `done`) is ground truth and
9
+ // drives the KERNEL-BACKED handler on a FRESH temp root — never a spawned mock.
10
+ //
11
+ // v1 SLICE (honesty, per the build spec): index/collection management + vector CRUD + REAL
12
+ // brute-force similarity query (all 3 metrics) + REAL metadata filtering + namespaces +
13
+ // describe_index_stats + host-split control/data-plane routing are modeled `done`. Sparse/hybrid
14
+ // search, integrated inference/embedding, bulk import, backups/restore, pod-based scaling, full
15
+ // collection-snapshot semantics, and gRPC-status-details error parity are left `todo` with
16
+ // vendor-shaped failure (unmodeled route -> 404) — a deliberate v1 scope cut, not an oversight;
17
+ // see README `## Coverage`.
18
+ //
19
+ // THE QUERY ENGINE IS A GENUINE STRONG DONE, NOT A STUB (the biggest structural difference from
20
+ // this repo's generative twins): exact brute-force top-K over stored vector values is
21
+ // deterministic, correct math — a faithful SUPERSET of Pinecone's real approximate-NN recall, not
22
+ // a lesser placeholder. Integrated embedding (`createIndexForModel`, `/records/…`, `/embed`,
23
+ // `/rerank`) is filed as todos. See pinecone-similarity.ts / pinecone-filter.ts headers + README.
24
+ import { mkdtempSync, rmSync } from 'node:fs';
25
+ import { tmpdir } from 'node:os';
26
+ import { join } from 'node:path';
27
+ import { checkCapabilities, verifyBoundary } from '@volter/world-tooling';
28
+ import { handlePineconeTwinRequest } from "./pinecone-twin.js";
29
+ import { checkPineconeConformance } from "./pinecone-conformance.js";
30
+ import { syncPineconeFromReal } from "./pinecone-connector.js";
31
+ /** Run a sequence of real Pinecone-twin requests against an isolated root; return all responses. */
32
+ async function withRoot(steps) {
33
+ const root = mkdtempSync(join(tmpdir(), 'pinecone-cap-'));
34
+ const h = (s) => handlePineconeTwinRequest({
35
+ method: s.m,
36
+ path: s.p,
37
+ body: s.b === undefined ? undefined : JSON.stringify(s.b),
38
+ root,
39
+ ...(s.index !== undefined ? { index: s.index } : {}),
40
+ ...(s.host !== undefined ? { host: s.host } : {}),
41
+ ...(s.readOnly !== undefined ? { readOnly: s.readOnly } : {}),
42
+ });
43
+ try {
44
+ return await verifyBoundary('pinecone.withRoot', () => steps(h));
45
+ }
46
+ finally {
47
+ rmSync(root, { recursive: true, force: true });
48
+ }
49
+ }
50
+ const ok = (r) => r.status >= 200 && r.status < 300;
51
+ const field = (r, k) => r.body?.[k];
52
+ /** Create an index via the control plane; return its full response body (incl. `host`). */
53
+ async function createIndex(h, name, dimension, metric = 'cosine') {
54
+ const r = await h({ m: 'POST', p: '/indexes', b: { name, dimension, metric, spec: { serverless: { cloud: 'aws', region: 'us-east-1' } } } });
55
+ return r.body;
56
+ }
57
+ const done = (id, area, title, dimension, tier, verify) => ({ id, area, title, dimension, tier, expected: 'done', verify });
58
+ const todo = (id, area, title, dimension, tier) => ({ id, area, title, dimension, tier, expected: 'todo' });
59
+ export const PINECONE_CAPABILITIES = [
60
+ todo('pinecone.records.integrated_surface', 'vectors', 'Integrated-embedding surface: createIndexForModel and POST /records/{namespace}/upsert|search, embedding the supplied text into deterministic pseudo-vectors (POST /embed and /rerank are filed separately as pinecone.inference.*)', 'api', 'niche'),
61
+ // ── INDEXES (control plane) ─────────────────────────────────────────────────────────────
62
+ done('pinecone.indexes.create', 'indexes', 'POST /indexes creates an index -> 201 with dimension/metric/host/status.ready', 'api', 'core', () => withRoot(async (h) => {
63
+ const r = await h({ m: 'POST', p: '/indexes', b: { name: 'products', dimension: 4, metric: 'cosine', spec: { serverless: { cloud: 'aws', region: 'us-east-1' } } } });
64
+ return r.status === 201 && field(r, 'name') === 'products' && field(r, 'dimension') === 4 && field(r, 'metric') === 'cosine'
65
+ && typeof field(r, 'host') === 'string' && field(r, 'host').length > 0
66
+ && field(r, 'status')?.ready === true;
67
+ })),
68
+ done('pinecone.indexes.create_requires_name_dimension', 'indexes', 'POST /indexes without name or dimension -> 400 {error:{code,message},status}', 'api', 'core', () => withRoot(async (h) => {
69
+ const r = await h({ m: 'POST', p: '/indexes', b: { metric: 'cosine' } });
70
+ return r.status === 400 && typeof field(r, 'error')?.code === 'string' && field(r, 'status') === 400;
71
+ })),
72
+ done('pinecone.indexes.list', 'indexes', 'GET /indexes -> {indexes:[...]} incl. the created index', 'api', 'core', () => withRoot(async (h) => {
73
+ await createIndex(h, 'catalog', 3);
74
+ const r = await h({ m: 'GET', p: '/indexes' });
75
+ const names = field(r, 'indexes').map((i) => i.name);
76
+ return ok(r) && names.includes('catalog');
77
+ })),
78
+ done('pinecone.indexes.describe', 'indexes', 'GET /indexes/:name round-trips fields set at create', 'api', 'core', () => withRoot(async (h) => {
79
+ await createIndex(h, 'docs', 8, 'dotproduct');
80
+ const r = await h({ m: 'GET', p: '/indexes/docs' });
81
+ return ok(r) && field(r, 'name') === 'docs' && field(r, 'dimension') === 8 && field(r, 'metric') === 'dotproduct';
82
+ })),
83
+ done('pinecone.indexes.describe_missing_404', 'indexes', 'GET /indexes/:name for an unknown index -> 404 {error:{code,message}}', 'api', 'core', () => withRoot(async (h) => {
84
+ const r = await h({ m: 'GET', p: '/indexes/does-not-exist' });
85
+ return r.status === 404 && typeof field(r, 'error')?.message === 'string';
86
+ })),
87
+ done('pinecone.indexes.delete', 'indexes', 'DELETE /indexes/:name removes it (2xx), then describe -> 404', 'api', 'core', () => withRoot(async (h) => {
88
+ await createIndex(h, 'temp-index', 2);
89
+ const del = await h({ m: 'DELETE', p: '/indexes/temp-index' });
90
+ const get = await h({ m: 'GET', p: '/indexes/temp-index' });
91
+ return ok(del) && get.status === 404;
92
+ })),
93
+ done('pinecone.indexes.configure', 'indexes', 'PATCH /indexes/:name updates deletionProtection/tags; changing dimension is rejected (400)', 'api', 'common', () => withRoot(async (h) => {
94
+ await createIndex(h, 'configurable', 4);
95
+ const patch = await h({ m: 'PATCH', p: '/indexes/configurable', b: { deletion_protection: 'enabled', tags: { env: 'test' } } });
96
+ const dimAttempt = await h({ m: 'PATCH', p: '/indexes/configurable', b: { dimension: 99 } });
97
+ return ok(patch) && field(patch, 'deletion_protection') === 'enabled' && field(patch, 'tags')?.env === 'test' && dimAttempt.status === 400;
98
+ })),
99
+ done('pinecone.indexes.host_deterministic', 'indexes', 'The minted per-index host is stable across repeated describes for the same (index,root)', 'api', 'common', () => withRoot(async (h) => {
100
+ await createIndex(h, 'stable-host', 4);
101
+ const a = await h({ m: 'GET', p: '/indexes/stable-host' });
102
+ const b = await h({ m: 'GET', p: '/indexes/stable-host' });
103
+ const hostA = field(a, 'host');
104
+ return ok(a) && typeof hostA === 'string' && hostA.length > 0 && hostA === field(b, 'host') && hostA.includes('stable-host');
105
+ })),
106
+ // ── COLLECTIONS (control plane) ─────────────────────────────────────────────────────────
107
+ done('pinecone.collections.create', 'collections', 'POST /collections {name,source} snapshots an existing index\'s dimension -> 201 (missing source -> 400)', 'api', 'common', () => withRoot(async (h) => {
108
+ await createIndex(h, 'src-index', 6);
109
+ const r = await h({ m: 'POST', p: '/collections', b: { name: 'snap-1', source: 'src-index' } });
110
+ const missing = await h({ m: 'POST', p: '/collections', b: { name: 'snap-bad' } });
111
+ return r.status === 201 && field(r, 'name') === 'snap-1' && field(r, 'dimension') === 6 && missing.status === 400;
112
+ })),
113
+ done('pinecone.collections.list', 'collections', 'GET /collections -> {collections:[...]} incl. the created collection', 'api', 'niche', () => withRoot(async (h) => {
114
+ await createIndex(h, 'src-index-2', 4);
115
+ await h({ m: 'POST', p: '/collections', b: { name: 'listed-collection', source: 'src-index-2' } });
116
+ const r = await h({ m: 'GET', p: '/collections' });
117
+ return ok(r) && field(r, 'collections').some((c) => c.name === 'listed-collection');
118
+ })),
119
+ done('pinecone.collections.describe', 'collections', 'GET /collections/:name round-trips (404 negative)', 'api', 'niche', () => withRoot(async (h) => {
120
+ await createIndex(h, 'src-index-3', 4);
121
+ await h({ m: 'POST', p: '/collections', b: { name: 'described-collection', source: 'src-index-3' } });
122
+ const r = await h({ m: 'GET', p: '/collections/described-collection' });
123
+ const missing = await h({ m: 'GET', p: '/collections/does-not-exist' });
124
+ return ok(r) && field(r, 'name') === 'described-collection' && missing.status === 404;
125
+ })),
126
+ done('pinecone.collections.delete', 'collections', 'DELETE /collections/:name removes it (2xx), then describe -> 404', 'api', 'niche', () => withRoot(async (h) => {
127
+ await createIndex(h, 'src-index-4', 4);
128
+ await h({ m: 'POST', p: '/collections', b: { name: 'deletable-collection', source: 'src-index-4' } });
129
+ const del = await h({ m: 'DELETE', p: '/collections/deletable-collection' });
130
+ const get = await h({ m: 'GET', p: '/collections/deletable-collection' });
131
+ return ok(del) && get.status === 404;
132
+ })),
133
+ // ── VECTORS (data plane) ────────────────────────────────────────────────────────────────
134
+ done('pinecone.vectors.upsert', 'vectors', 'POST /vectors/upsert -> {upsertedCount}; fetch round-trips values+metadata', 'api', 'core', () => withRoot(async (h) => {
135
+ const idx = await createIndex(h, 'upsert-idx', 3);
136
+ const up = await h({ index: idx.name, m: 'POST', p: '/vectors/upsert', b: { vectors: [{ id: 'v1', values: [1, 2, 3], metadata: { color: 'red' } }, { id: 'v2', values: [4, 5, 6] }] } });
137
+ const fetched = await h({ index: idx.name, m: 'GET', p: '/vectors/fetch?ids=v1,v2' });
138
+ const vectors = field(fetched, 'vectors');
139
+ return field(up, 'upsertedCount') === 2 && JSON.stringify(vectors.v1.values) === JSON.stringify([1, 2, 3]) && vectors.v1.metadata.color === 'red' && JSON.stringify(vectors.v2.values) === JSON.stringify([4, 5, 6]);
140
+ })),
141
+ done('pinecone.vectors.upsert_dimension_mismatch', 'vectors', 'Upserting a vector whose values.length != index dimension -> 400 {code:3,message}', 'api', 'common', () => withRoot(async (h) => {
142
+ const idx = await createIndex(h, 'dim-idx', 5);
143
+ const r = await h({ index: idx.name, m: 'POST', p: '/vectors/upsert', b: { vectors: [{ id: 'bad', values: [1, 2] }] } });
144
+ return r.status === 400 && field(r, 'code') === 3 && typeof field(r, 'message') === 'string';
145
+ })),
146
+ done('pinecone.vectors.fetch', 'vectors', 'GET /vectors/fetch returns only the requested, existing ids with exact values', 'api', 'core', () => withRoot(async (h) => {
147
+ const idx = await createIndex(h, 'fetch-idx', 2);
148
+ await h({ index: idx.name, m: 'POST', p: '/vectors/upsert', b: { vectors: [{ id: 'a', values: [1, 1] }, { id: 'b', values: [2, 2] }] } });
149
+ const r = await h({ index: idx.name, m: 'GET', p: '/vectors/fetch?ids=a' });
150
+ const vectors = field(r, 'vectors');
151
+ return ok(r) && Object.keys(vectors).length === 1 && JSON.stringify(vectors.a.values) === JSON.stringify([1, 1]);
152
+ })),
153
+ done('pinecone.vectors.fetch_unknown_empty', 'vectors', 'GET /vectors/fetch for unknown ids -> 200 {vectors:{}} (never a 404)', 'api', 'common', () => withRoot(async (h) => {
154
+ const idx = await createIndex(h, 'fetch-empty-idx', 2);
155
+ await h({ index: idx.name, m: 'POST', p: '/vectors/upsert', b: { vectors: [{ id: 'exists', values: [1, 1] }] } });
156
+ const r = await h({ index: idx.name, m: 'GET', p: '/vectors/fetch?ids=nope-1,nope-2' });
157
+ return r.status === 200 && Object.keys(field(r, 'vectors')).length === 0;
158
+ })),
159
+ done('pinecone.vectors.update', 'vectors', 'POST /vectors/update merges setMetadata and replaces values on an existing vector', 'api', 'common', () => withRoot(async (h) => {
160
+ const idx = await createIndex(h, 'update-idx', 2);
161
+ await h({ index: idx.name, m: 'POST', p: '/vectors/upsert', b: { vectors: [{ id: 'u1', values: [1, 1], metadata: { a: 1 } }] } });
162
+ await h({ index: idx.name, m: 'POST', p: '/vectors/update', b: { id: 'u1', values: [9, 9], setMetadata: { b: 2 } } });
163
+ const r = await h({ index: idx.name, m: 'GET', p: '/vectors/fetch?ids=u1' });
164
+ const v = field(r, 'vectors').u1;
165
+ return JSON.stringify(v.values) === JSON.stringify([9, 9]) && v.metadata.a === 1 && v.metadata.b === 2;
166
+ })),
167
+ done('pinecone.vectors.delete_by_ids', 'vectors', 'POST /vectors/delete {ids} removes exactly those ids', 'api', 'core', () => withRoot(async (h) => {
168
+ const idx = await createIndex(h, 'del-ids-idx', 2);
169
+ await h({ index: idx.name, m: 'POST', p: '/vectors/upsert', b: { vectors: [{ id: 'keep', values: [1, 1] }, { id: 'gone', values: [2, 2] }] } });
170
+ await h({ index: idx.name, m: 'POST', p: '/vectors/delete', b: { ids: ['gone'] } });
171
+ const r = await h({ index: idx.name, m: 'GET', p: '/vectors/fetch?ids=keep,gone' });
172
+ const vectors = field(r, 'vectors');
173
+ return Object.keys(vectors).length === 1 && !!vectors.keep;
174
+ })),
175
+ done('pinecone.vectors.delete_all', 'vectors', 'POST /vectors/delete {deleteAll:true} clears ONLY the targeted namespace, not the whole index', 'api', 'common', () => withRoot(async (h) => {
176
+ const idx = await createIndex(h, 'del-all-idx', 2);
177
+ await h({ index: idx.name, m: 'POST', p: '/vectors/upsert', b: { namespace: 'ns-a', vectors: [{ id: 'x', values: [1, 1] }] } });
178
+ await h({ index: idx.name, m: 'POST', p: '/vectors/upsert', b: { namespace: 'ns-b', vectors: [{ id: 'y', values: [2, 2] }] } });
179
+ await h({ index: idx.name, m: 'POST', p: '/vectors/delete', b: { namespace: 'ns-a', deleteAll: true } });
180
+ const listA = await h({ index: idx.name, m: 'GET', p: '/vectors/list?namespace=ns-a' });
181
+ const listB = await h({ index: idx.name, m: 'GET', p: '/vectors/list?namespace=ns-b' });
182
+ return field(listA, 'vectors').length === 0 && field(listB, 'vectors').length === 1;
183
+ })),
184
+ done('pinecone.vectors.delete_by_filter', 'vectors', 'POST /vectors/delete {filter} removes only vectors whose metadata matches the REAL filter evaluator', 'api', 'common', () => withRoot(async (h) => {
185
+ const idx = await createIndex(h, 'del-filter-idx', 2);
186
+ await h({ index: idx.name, m: 'POST', p: '/vectors/upsert', b: { vectors: [{ id: 'stale', values: [1, 1], metadata: { status: 'stale' } }, { id: 'fresh', values: [2, 2], metadata: { status: 'fresh' } }] } });
187
+ await h({ index: idx.name, m: 'POST', p: '/vectors/delete', b: { filter: { status: 'stale' } } });
188
+ const r = await h({ index: idx.name, m: 'GET', p: '/vectors/fetch?ids=stale,fresh' });
189
+ const vectors = field(r, 'vectors');
190
+ return Object.keys(vectors).length === 1 && !!vectors.fresh;
191
+ })),
192
+ done('pinecone.vectors.list', 'vectors', 'GET /vectors/list returns ids in the namespace, filterable by prefix', 'api', 'common', () => withRoot(async (h) => {
193
+ const idx = await createIndex(h, 'list-idx', 2);
194
+ await h({ index: idx.name, m: 'POST', p: '/vectors/upsert', b: { vectors: [{ id: 'doc-1', values: [1, 1] }, { id: 'doc-2', values: [2, 2] }, { id: 'other-1', values: [3, 3] }] } });
195
+ const r = await h({ index: idx.name, m: 'GET', p: '/vectors/list?prefix=doc-' });
196
+ const ids = field(r, 'vectors').map((v) => v.id).sort();
197
+ return JSON.stringify(ids) === JSON.stringify(['doc-1', 'doc-2']);
198
+ })),
199
+ // ── QUERY (data plane — REAL similarity + REAL filter; the pack's signature strength) ────
200
+ done('pinecone.query.by_vector_cosine', 'query', 'POST /query with metric=cosine ranks a small-magnitude PERFECTLY-aligned vector above a larger-magnitude, less-aligned one', 'api', 'core', () => withRoot(async (h) => {
201
+ // Crafted so cosine and dotproduct DISAGREE on the winner (see query.metric_dotproduct):
202
+ // 'a'=[0.5,0] is perfectly aligned with Q=[1,0] (cosine=1.0) but has LOW dot product (0.5);
203
+ // 'b'=[2,1.9] is less aligned (cosine~=0.72) but has HIGH dot product (2.0). Under cosine,
204
+ // 'a' must win — a metric-ignoring (dotproduct-only) implementation would return 'b' first
205
+ // and this assertion would fail.
206
+ const idx = await createIndex(h, 'cosine-idx', 2, 'cosine');
207
+ await h({ index: idx.name, m: 'POST', p: '/vectors/upsert', b: { vectors: [{ id: 'a', values: [0.5, 0] }, { id: 'b', values: [2, 1.9] }] } });
208
+ const r = await h({ index: idx.name, m: 'POST', p: '/query', b: { vector: [1, 0], topK: 2 } });
209
+ const matches = field(r, 'matches');
210
+ return ok(r) && matches.length === 2 && matches[0].id === 'a' && matches[0].score > matches[1].score;
211
+ })),
212
+ done('pinecone.query.by_id', 'query', 'POST /query with an id (instead of a vector) queries using that vector\'s own values and EXCLUDES it from the results (⚠ doc-UNVERIFIED convention)', 'api', 'common', () => withRoot(async (h) => {
213
+ const idx = await createIndex(h, 'by-id-idx', 2, 'cosine');
214
+ await h({ index: idx.name, m: 'POST', p: '/vectors/upsert', b: { vectors: [{ id: 'origin', values: [1, 0] }, { id: 'neighbor', values: [1, 0.01] }] } });
215
+ const r = await h({ index: idx.name, m: 'POST', p: '/query', b: { id: 'origin', topK: 5 } });
216
+ const matches = field(r, 'matches');
217
+ return ok(r) && matches.length === 1 && matches[0].id === 'neighbor';
218
+ })),
219
+ done('pinecone.query.metric_dotproduct', 'query', 'POST /query with metric=dotproduct ranks the SAME crafted pair in the OPPOSITE order from cosine', 'api', 'core', () => withRoot(async (h) => {
220
+ const idx = await createIndex(h, 'dot-idx', 2, 'dotproduct');
221
+ await h({ index: idx.name, m: 'POST', p: '/vectors/upsert', b: { vectors: [{ id: 'a', values: [0.5, 0] }, { id: 'b', values: [2, 1.9] }] } });
222
+ const r = await h({ index: idx.name, m: 'POST', p: '/query', b: { vector: [1, 0], topK: 2 } });
223
+ const matches = field(r, 'matches');
224
+ // 'b' wins on raw dot product (2.0 > 0.5) — the OPPOSITE winner from
225
+ // pinecone.query.by_vector_cosine's cosine ranking over the identical vector set. A metric-
226
+ // ignoring implementation (always ranking by cosine, or ignoring metric entirely) fails here.
227
+ return ok(r) && matches[0].id === 'b' && matches[0].score === 2;
228
+ })),
229
+ done('pinecone.query.metric_euclidean', 'query', 'POST /query with metric=euclidean sorts ASCENDING by squared L2 distance (lower = closer, ⚠ doc-UNVERIFIED exact wire score)', 'api', 'common', () => withRoot(async (h) => {
230
+ const idx = await createIndex(h, 'euclid-idx', 2, 'euclidean');
231
+ await h({ index: idx.name, m: 'POST', p: '/vectors/upsert', b: { vectors: [{ id: 'near', values: [1, 0] }, { id: 'far', values: [3, 0] }] } });
232
+ const r = await h({ index: idx.name, m: 'POST', p: '/query', b: { vector: [0, 0], topK: 2 } });
233
+ const matches = field(r, 'matches');
234
+ return ok(r) && matches[0].id === 'near' && matches[0].score === 1 && matches[1].id === 'far' && matches[1].score === 9;
235
+ })),
236
+ done('pinecone.query.topk_deterministic', 'query', 'Two identical queries return identical top-K; a different topK returns a genuinely different result', 'api', 'core', () => withRoot(async (h) => {
237
+ const idx = await createIndex(h, 'topk-idx', 2, 'cosine');
238
+ await h({ index: idx.name, m: 'POST', p: '/vectors/upsert', b: { vectors: [{ id: 'p', values: [1, 0] }, { id: 'q', values: [0.9, 0.1] }, { id: 'z', values: [-1, 0] }] } });
239
+ const call1 = await h({ index: idx.name, m: 'POST', p: '/query', b: { vector: [1, 0], topK: 1 } });
240
+ const call2 = await h({ index: idx.name, m: 'POST', p: '/query', b: { vector: [1, 0], topK: 1 } });
241
+ const call3 = await h({ index: idx.name, m: 'POST', p: '/query', b: { vector: [1, 0], topK: 2 } });
242
+ const m1 = field(call1, 'matches').map((m) => m.id);
243
+ const m2 = field(call2, 'matches').map((m) => m.id);
244
+ const m3 = field(call3, 'matches').map((m) => m.id);
245
+ return JSON.stringify(m1) === JSON.stringify(m2) && m1.length === 1 && m3.length === 2 && JSON.stringify(m1) !== JSON.stringify(m3);
246
+ })),
247
+ done('pinecone.query.include_flags', 'query', 'includeValues/includeMetadata default false; setting them true adds those fields to each match', 'api', 'common', () => withRoot(async (h) => {
248
+ const idx = await createIndex(h, 'include-idx', 2, 'cosine');
249
+ await h({ index: idx.name, m: 'POST', p: '/vectors/upsert', b: { vectors: [{ id: 'v', values: [1, 0], metadata: { k: 'v' } }] } });
250
+ const bare = await h({ index: idx.name, m: 'POST', p: '/query', b: { vector: [1, 0], topK: 1 } });
251
+ const full = await h({ index: idx.name, m: 'POST', p: '/query', b: { vector: [1, 0], topK: 1, includeValues: true, includeMetadata: true } });
252
+ const bareMatch = field(bare, 'matches')[0];
253
+ const fullMatch = field(full, 'matches')[0];
254
+ return bareMatch.values === undefined && bareMatch.metadata === undefined && JSON.stringify(fullMatch.values) === JSON.stringify([1, 0]) && fullMatch.metadata.k === 'v';
255
+ })),
256
+ done('pinecone.query.filter_eq', 'query', 'A metadata filter EXCLUDES the otherwise-nearest vector, changing which match wins', 'api', 'core', () => withRoot(async (h) => {
257
+ // 'near' is the TRUE nearest neighbor to the query vector but is filtered out by metadata —
258
+ // a filter-ignoring implementation would return 'near' and this assertion fails.
259
+ const idx = await createIndex(h, 'filter-eq-idx', 2, 'cosine');
260
+ await h({ index: idx.name, m: 'POST', p: '/vectors/upsert', b: { vectors: [{ id: 'near', values: [1, 0], metadata: { category: 'excluded' } }, { id: 'far', values: [0.1, 1], metadata: { category: 'included' } }] } });
261
+ const r = await h({ index: idx.name, m: 'POST', p: '/query', b: { vector: [1, 0], topK: 1, filter: { category: 'included' } } });
262
+ const matches = field(r, 'matches');
263
+ return ok(r) && matches.length === 1 && matches[0].id === 'far';
264
+ })),
265
+ done('pinecone.query.filter_operators', 'query', '$gt/$gte/$lt/$lte/$in/$nin each select the correct subset over numeric/enum metadata', 'api', 'common', () => withRoot(async (h) => {
266
+ const idx = await createIndex(h, 'filter-ops-idx', 2, 'cosine');
267
+ await h({ index: idx.name, m: 'POST', p: '/vectors/upsert', b: { vectors: [
268
+ { id: 'lo', values: [1, 0], metadata: { score: 1, tag: 'a' } },
269
+ { id: 'mid', values: [1, 0], metadata: { score: 5, tag: 'b' } },
270
+ { id: 'hi', values: [1, 0], metadata: { score: 10, tag: 'c' } },
271
+ ] } });
272
+ const gt = await h({ index: idx.name, m: 'POST', p: '/query', b: { vector: [1, 0], topK: 10, filter: { score: { $gt: 3 } } } });
273
+ const lte = await h({ index: idx.name, m: 'POST', p: '/query', b: { vector: [1, 0], topK: 10, filter: { score: { $lte: 5 } } } });
274
+ const inOp = await h({ index: idx.name, m: 'POST', p: '/query', b: { vector: [1, 0], topK: 10, filter: { tag: { $in: ['a', 'c'] } } } });
275
+ const nin = await h({ index: idx.name, m: 'POST', p: '/query', b: { vector: [1, 0], topK: 10, filter: { tag: { $nin: ['a', 'c'] } } } });
276
+ const ids = (r) => field(r, 'matches').map((m) => m.id).sort();
277
+ return JSON.stringify(ids(gt)) === JSON.stringify(['hi', 'mid']) && JSON.stringify(ids(lte)) === JSON.stringify(['lo', 'mid']) && JSON.stringify(ids(inOp)) === JSON.stringify(['hi', 'lo']) && JSON.stringify(ids(nin)) === JSON.stringify(['mid']);
278
+ })),
279
+ done('pinecone.query.filter_and_or', 'query', '$and requires every clause; $or requires at least one', 'api', 'common', () => withRoot(async (h) => {
280
+ const idx = await createIndex(h, 'filter-andor-idx', 2, 'cosine');
281
+ await h({ index: idx.name, m: 'POST', p: '/vectors/upsert', b: { vectors: [
282
+ { id: 'both', values: [1, 0], metadata: { category: 'x', score: 5 } },
283
+ { id: 'cat-only', values: [1, 0], metadata: { category: 'x', score: 1 } },
284
+ { id: 'score-only', values: [1, 0], metadata: { category: 'y', score: 9 } },
285
+ { id: 'neither', values: [1, 0], metadata: { category: 'y', score: 1 } },
286
+ ] } });
287
+ const and = await h({ index: idx.name, m: 'POST', p: '/query', b: { vector: [1, 0], topK: 10, filter: { $and: [{ category: 'x' }, { score: { $gte: 5 } }] } } });
288
+ const or = await h({ index: idx.name, m: 'POST', p: '/query', b: { vector: [1, 0], topK: 10, filter: { $or: [{ category: 'x' }, { score: { $gte: 9 } }] } } });
289
+ const ids = (r) => field(r, 'matches').map((m) => m.id).sort();
290
+ return JSON.stringify(ids(and)) === JSON.stringify(['both']) && JSON.stringify(ids(or)) === JSON.stringify(['both', 'cat-only', 'score-only']);
291
+ })),
292
+ // ── NAMESPACES / STATS ──────────────────────────────────────────────────────────────────
293
+ done('pinecone.namespaces.isolation', 'namespaces', 'The same vector id in two namespaces holds independent values; fetch/list never leak across namespaces', 'api', 'core', () => withRoot(async (h) => {
294
+ const idx = await createIndex(h, 'ns-idx', 2);
295
+ await h({ index: idx.name, m: 'POST', p: '/vectors/upsert', b: { namespace: 'tenant-a', vectors: [{ id: 'shared-id', values: [1, 1] }] } });
296
+ await h({ index: idx.name, m: 'POST', p: '/vectors/upsert', b: { namespace: 'tenant-b', vectors: [{ id: 'shared-id', values: [2, 2] }] } });
297
+ const a = await h({ index: idx.name, m: 'GET', p: '/vectors/fetch?ids=shared-id&namespace=tenant-a' });
298
+ const b = await h({ index: idx.name, m: 'GET', p: '/vectors/fetch?ids=shared-id&namespace=tenant-b' });
299
+ const listA = await h({ index: idx.name, m: 'GET', p: '/vectors/list?namespace=tenant-a' });
300
+ const va = field(a, 'vectors')['shared-id'];
301
+ const vb = field(b, 'vectors')['shared-id'];
302
+ return JSON.stringify(va.values) === JSON.stringify([1, 1]) && JSON.stringify(vb.values) === JSON.stringify([2, 2]) && field(listA, 'vectors').length === 1;
303
+ })),
304
+ done('pinecone.stats.describe_index_stats', 'stats', 'POST /describe_index_stats aggregates per-namespace vectorCount + totalVectorCount + dimension', 'api', 'common', () => withRoot(async (h) => {
305
+ const idx = await createIndex(h, 'stats-idx', 3);
306
+ await h({ index: idx.name, m: 'POST', p: '/vectors/upsert', b: { namespace: 'x', vectors: [{ id: '1', values: [1, 1, 1] }, { id: '2', values: [2, 2, 2] }] } });
307
+ await h({ index: idx.name, m: 'POST', p: '/vectors/upsert', b: { namespace: 'y', vectors: [{ id: '3', values: [3, 3, 3] }] } });
308
+ const r = await h({ index: idx.name, m: 'POST', p: '/describe_index_stats' });
309
+ const namespaces = field(r, 'namespaces');
310
+ return ok(r) && namespaces.x.vectorCount === 2 && namespaces.y.vectorCount === 1 && field(r, 'totalVectorCount') === 3 && field(r, 'dimension') === 3;
311
+ })),
312
+ // ── HOST-SPLIT ROUTER ────────────────────────────────────────────────────────────────────
313
+ done('pinecone.control_plane.host_split_routing', 'control_plane', 'A request with host=api.pinecone.io (or no host, /indexes|/collections path fallback) resolves to the control plane and its {error:{code,message},status} envelope', 'api', 'core', () => withRoot(async (h) => {
314
+ const r = await h({ m: 'GET', p: '/indexes/does-not-exist', host: 'api.pinecone.io' });
315
+ return r.status === 404 && typeof field(r, 'error')?.code === 'string' && field(r, 'status') === 404;
316
+ })),
317
+ done('pinecone.data_plane.host_split_routing', 'data_plane', 'A request Host matching a minted per-index host round-trips to that index\'s data plane WITHOUT any explicit index field', 'api', 'core', () => withRoot(async (h) => {
318
+ const idx = await createIndex(h, 'host-routed-idx', 2);
319
+ const mintedHost = idx.host;
320
+ if (!mintedHost)
321
+ return false;
322
+ const upsert = await h({ m: 'POST', p: '/vectors/upsert', host: mintedHost, b: { vectors: [{ id: 'v1', values: [1, 0] }] } });
323
+ const fetched = await h({ m: 'GET', p: '/vectors/fetch?ids=v1', host: mintedHost });
324
+ return ok(upsert) && ok(fetched) && !!field(fetched, 'vectors').v1;
325
+ })),
326
+ // ── SAFETY / HONESTY / STATE ─────────────────────────────────────────────────────────────
327
+ done('pinecone.read_only.rejects_writes', 'safety', 'readOnly mode rejects a write (create index -> 405) but allows reads AND read-shaped POSTs (query)', 'api', 'common', () => withRoot(async (h) => {
328
+ const idx = await createIndex(h, 'ro-idx', 2);
329
+ const write = await h({ m: 'POST', p: '/indexes', readOnly: true, b: { name: 'blocked', dimension: 2 } });
330
+ const readList = await h({ m: 'GET', p: '/indexes', readOnly: true });
331
+ const readQuery = await h({ index: idx.name, m: 'POST', p: '/query', readOnly: true, b: { vector: [1, 0], topK: 1 } });
332
+ return write.status === 405 && readList.status === 200 && readQuery.status === 200;
333
+ })),
334
+ done('pinecone.unmodeled_route.404', 'safety', 'An unmodeled control-plane route returns Pinecone\'s error envelope (never a fabricated success)', 'api', 'common', () => withRoot(async (h) => {
335
+ const r = await h({ m: 'GET', p: '/totally-unknown-resource', host: 'api.pinecone.io' });
336
+ return r.status === 404 && typeof field(r, 'error')?.message === 'string';
337
+ })),
338
+ done('pinecone.state.kernel_persisted', 'state', 'State is kernel-folded — a vector upserted in one call is visible, with correct values, in the next', 'api', 'core', () => withRoot(async (h) => {
339
+ const idx = await createIndex(h, 'persist-idx', 2);
340
+ const name = idx.name;
341
+ // a dead twin returns {} — the name must be a REAL non-empty string first, or the final
342
+ // comparison is vacuous.
343
+ if (typeof name !== 'string' || name.length === 0)
344
+ return false;
345
+ await h({ index: name, m: 'POST', p: '/vectors/upsert', b: { vectors: [{ id: 'persisted', values: [7, 8] }] } });
346
+ const r = await h({ index: name, m: 'GET', p: '/vectors/fetch?ids=persisted' });
347
+ return JSON.stringify(field(r, 'vectors').persisted?.values) === JSON.stringify([7, 8]);
348
+ })),
349
+ // ── CONFORMANCE / CONNECTOR ──────────────────────────────────────────────────────────────
350
+ done('pinecone.conformance.snapshot', 'conformance', 'Conformance snapshot covers every resource type + the core control/data-plane endpoints', 'api', 'common', () => {
351
+ const report = checkPineconeConformance();
352
+ return report.ok && report.endpointsChecked >= 15 && report.resourceTypesChecked === 3;
353
+ }),
354
+ done('pinecone.connector.sync.entrypoint', 'connector', 'syncPineconeFromReal folds indexes + vectors into the twin, idempotent, read-through-handler', 'connector', 'core', async () => {
355
+ const root = mkdtempSync(join(tmpdir(), 'pinecone-conn-'));
356
+ try {
357
+ const client = {
358
+ listIndexes: async () => ({ indexes: [{ name: 'real-index', dimension: 3, metric: 'cosine' }] }),
359
+ listVectors: async () => ({ vectors: [{ index: 'real-index', namespace: '', id: 'real-vec-1', values: [1, 2, 3], metadata: { source: 'real' } }] }),
360
+ };
361
+ const first = await syncPineconeFromReal(client, { root, budgetOptions: { root } });
362
+ if (first.observed !== 2 || first.deltasAppended < 2)
363
+ return false;
364
+ // a re-pull of identical state is a no-op (shadow-diff dedup) -> 0 deltas appended
365
+ const second = await syncPineconeFromReal(client, { root, budgetOptions: { root } });
366
+ if (second.deltasAppended !== 0)
367
+ return false;
368
+ // the pulled data is now visible via the twin handler (real fold, not a count)
369
+ const described = await handlePineconeTwinRequest({ method: 'GET', path: '/indexes/real-index', host: 'api.pinecone.io', root });
370
+ const fetched = await handlePineconeTwinRequest({ method: 'GET', path: '/vectors/fetch?ids=real-vec-1', index: 'real-index', root });
371
+ return described.status === 200 && described.body.dimension === 3 && JSON.stringify(fetched.body.vectors['real-vec-1']?.values) === JSON.stringify([1, 2, 3]);
372
+ }
373
+ finally {
374
+ rmSync(root, { recursive: true, force: true });
375
+ }
376
+ }),
377
+ done('pinecone.connector.empty_client', 'connector', 'syncPineconeFromReal with an empty client observes nothing', 'connector', 'common', async () => {
378
+ const root = mkdtempSync(join(tmpdir(), 'pinecone-conn-empty-'));
379
+ try {
380
+ const result = await syncPineconeFromReal({}, { root, budgetOptions: { root } });
381
+ return result.observed === 0 && result.deltasAppended === 0;
382
+ }
383
+ finally {
384
+ rmSync(root, { recursive: true, force: true });
385
+ }
386
+ }),
387
+ // ── TODO: the rest of the Pinecone v1 surface (honest denominator) ──────────────────────
388
+ todo('pinecone.vectors.upsert_sparse', 'vectors', 'Sparse vector values on upsert (hybrid-search sparse-dense records)', 'api', 'common'),
389
+ todo('pinecone.query.sparse_hybrid', 'query', 'Hybrid dense+sparse query scoring', 'api', 'common'),
390
+ todo('pinecone.query.rerank', 'query', 'Query-time result reranking against a rerank model', 'api', 'common'),
391
+ todo('pinecone.filters.exists', 'filters', '$exists metadata-filter operator', 'api', 'common'),
392
+ todo('pinecone.filters.deep_nested', 'filters', 'Deep dot-path / nested-object metadata filter addressing', 'api', 'common'),
393
+ todo('pinecone.filters.null_semantics', 'filters', 'Explicit null-value metadata comparison edge semantics', 'api', 'niche'),
394
+ todo('pinecone.indexes.configure_pods', 'indexes', 'PATCH /indexes/:name pod-based spec.pod.replicas/podType scaling', 'api', 'common'),
395
+ todo('pinecone.indexes.pod_types', 'indexes', 'The real pod-type catalog (p1/p2/s1 etc.) for pod-based indexes', 'api', 'niche'),
396
+ todo('pinecone.indexes.serverless_vs_pod', 'indexes', 'Full serverless-vs-pod spec validation/branching on create', 'api', 'common'),
397
+ todo('pinecone.indexes.deletion_protection_enforced', 'indexes', 'deletionProtection:"enabled" actually BLOCKS DELETE /indexes/:name (currently stored but not enforced)', 'api', 'common'),
398
+ todo('pinecone.backups.create_list_restore', 'indexes', 'POST /indexes/:name/backups + GET /backups + create-index-from-backup', 'api', 'common'),
399
+ todo('pinecone.collections.from_index_snapshot', 'collections', 'A collection genuinely snapshots the SOURCE index\'s vector data at create time (v1: metadata-only record)', 'api', 'common'),
400
+ todo('pinecone.collections.restore', 'collections', 'POST /backups/{id}/create-index-style restore-a-collection-into-an-index flow', 'api', 'common'),
401
+ todo('pinecone.bulk.import_start', 'vectors', 'POST /bulk/imports starts an async bulk import job from object storage', 'api', 'common'),
402
+ todo('pinecone.bulk.import_status', 'vectors', 'GET /bulk/imports/:id status polling', 'api', 'common'),
403
+ todo('pinecone.bulk.import_list_cancel', 'vectors', 'GET /bulk/imports (list) + DELETE /bulk/imports/:id (cancel)', 'api', 'niche'),
404
+ todo('pinecone.inference.embed', 'vectors', 'POST /embed — integrated embedding of raw text into vectors', 'api', 'common'),
405
+ todo('pinecone.inference.rerank', 'query', 'POST /rerank — integrated cross-encoder reranking of a result set', 'api', 'common'),
406
+ todo('pinecone.namespaces.list', 'namespaces', 'GET /namespaces — enumerate namespaces as their own resource (today: derived only via describe_index_stats)', 'api', 'common'),
407
+ todo('pinecone.namespaces.delete', 'namespaces', 'DELETE /namespaces/:name — delete an entire namespace as its own operation', 'api', 'common'),
408
+ todo('pinecone.vectors.list_pagination_token', 'pagination', 'Real opaque paginationToken cursor fidelity on GET /vectors/list (twin always returns null)', 'api', 'common'),
409
+ todo('pinecone.query.top_k_max_limit', 'query', 'Real topK upper-bound enforcement (10000 dense / 1000 with values, per the docs) -> 400 over the limit', 'api', 'niche'),
410
+ todo('pinecone.errors.grpc_status_details', 'errors', 'Full gRPC-gateway `details[]` payload parity on data-plane errors (twin always returns [])', 'api', 'common'),
411
+ todo('pinecone.errors.control_plane_shape', 'errors', 'Independently confirm the exact control-plane error envelope against a live account (currently doc-UNVERIFIED, modeled per the build spec)', 'api', 'common'),
412
+ todo('pinecone.auth.api_key_401_parity', 'auth', 'Missing/invalid Api-Key header -> 401 parity (twin fakes auth locally; every request is accepted)', 'api', 'niche'),
413
+ todo('pinecone.auth.project_scoping', 'auth', 'Multi-project / multi-org API-key scoping and cross-project 403s', 'api', 'niche'),
414
+ todo('pinecone.stats.index_fullness_real', 'stats', 'A non-zero, organically-computed indexFullness (today: always 0 — no real infrastructure)', 'api', 'niche'),
415
+ todo('pinecone.connector.push', 'connector', 'Push locally-created indexes/vectors against the real API', 'connector', 'common'),
416
+ todo('pinecone.connector.pull_collections', 'connector', 'Connector: pull collections from the real account', 'connector', 'common'),
417
+ todo('pinecone.fixtures.seed', 'connector', 'Seed indexes/vectors from fixtures', 'connector', 'niche'),
418
+ // ── NITs filed from the skeptic review of the commit-3 build (non-blocking; current behavior
419
+ // stays as-is unchanged here — just tracked so a future pass makes a deliberate choice) ──
420
+ todo('pinecone.query.dimension_mismatch', 'query', 'squaredEuclideanDistance silently zero-pads mismatched-length query/candidate vectors instead of rejecting the query (pinecone-similarity.ts) — real Pinecone rejects a query vector whose length != index dimension; verify + fix or document as intentional leniency', 'api', 'niche'),
421
+ todo('pinecone.vectors.update_nonexistent', 'vectors', 'POST /vectors/update against a non-existent vector id returns 200 silently instead of a real vendor-shaped error/no-op signal — confirm real Pinecone behavior and match it', 'api', 'niche'),
422
+ ];
423
+ export const PINECONE_AREAS = [
424
+ 'indexes', 'collections', 'vectors', 'query', 'namespaces', 'filters', 'stats',
425
+ 'control_plane', 'data_plane', 'auth', 'pagination', 'errors', 'connector', 'conformance',
426
+ 'safety', 'state',
427
+ ];
428
+ export async function pineconeCapabilities() {
429
+ return checkCapabilities('pinecone', PINECONE_CAPABILITIES);
430
+ }
@@ -0,0 +1,7 @@
1
+ export type PineconeConformanceReport = {
2
+ ok: boolean;
3
+ endpointsChecked: number;
4
+ resourceTypesChecked: number;
5
+ violations: string[];
6
+ };
7
+ export declare function checkPineconeConformance(): PineconeConformanceReport;
@@ -0,0 +1,32 @@
1
+ // Pinecone conformance (dev-only; lazy-imported by the CLI, NEVER from index.ts/runtime — E2).
2
+ // Checks the twin's endpoint/resource inventory against the modeled Pinecone surface. Like
3
+ // replicate/fal, this is a self-referential snapshot check (against the twin's own declared
4
+ // inventory), not a systematic per-resource spec-diff — see docs/contributing/conformance.md's "2 spec" column note.
5
+ import { pineconeTwinSnapshot, PINECONE_RESOURCE_TYPES } from "./pinecone-twin.js";
6
+ export function checkPineconeConformance() {
7
+ const snapshot = pineconeTwinSnapshot();
8
+ const violations = [];
9
+ const stemFor = {
10
+ index: '/indexes',
11
+ collection: '/collections',
12
+ vector: '/vectors',
13
+ };
14
+ for (const type of PINECONE_RESOURCE_TYPES) {
15
+ const stem = stemFor[type];
16
+ if (!stem || !snapshot.implementedEndpoints.some((e) => e.includes(stem))) {
17
+ violations.push(`resource type '${type}' has no implemented endpoint`);
18
+ }
19
+ }
20
+ if (!snapshot.implementedEndpoints.some((e) => e === 'POST /query'))
21
+ violations.push('missing query endpoint');
22
+ if (!snapshot.implementedEndpoints.some((e) => e === 'POST /describe_index_stats'))
23
+ violations.push('missing describe_index_stats endpoint');
24
+ if (!snapshot.implementedEndpoints.some((e) => e === 'POST /indexes'))
25
+ violations.push('missing index create endpoint');
26
+ return {
27
+ ok: violations.length === 0,
28
+ endpointsChecked: snapshot.implementedEndpoints.length,
29
+ resourceTypesChecked: snapshot.resourceTypes.length,
30
+ violations,
31
+ };
32
+ }
@@ -0,0 +1,45 @@
1
+ import type { SyncResource } from '@volter/world-core';
2
+ import { type PineconeBudgetedOptions } from './pinecone-budget.js';
3
+ export type { PineconeBudgetedOptions };
4
+ export type PineconeRealIndex = {
5
+ name: string;
6
+ dimension?: number | null;
7
+ metric?: string | null;
8
+ spec?: unknown;
9
+ deletionProtection?: string | null;
10
+ tags?: Record<string, string> | null;
11
+ vectorType?: string | null;
12
+ };
13
+ export type PineconeRealVector = {
14
+ index: string;
15
+ namespace?: string | null;
16
+ id: string;
17
+ values?: number[] | null;
18
+ metadata?: Record<string, unknown> | null;
19
+ };
20
+ export interface PineconeLikeClient {
21
+ listIndexes?: () => Promise<{
22
+ indexes: PineconeRealIndex[];
23
+ }>;
24
+ listVectors?: () => Promise<{
25
+ vectors: PineconeRealVector[];
26
+ }>;
27
+ }
28
+ export declare function mapIndex(m: PineconeRealIndex): SyncResource;
29
+ export declare function mapVector(v: PineconeRealVector): SyncResource;
30
+ export declare function pullPineconeIndexes(rawClient: PineconeLikeClient, opts?: {
31
+ root?: string;
32
+ } & PineconeBudgetedOptions): Promise<SyncResource[]>;
33
+ export declare function pullPineconeVectors(rawClient: PineconeLikeClient, opts?: PineconeBudgetedOptions): Promise<SyncResource[]>;
34
+ /**
35
+ * D7 entry point: pull from real Pinecone (indexes + vectors) and fold EVERY domain into the twin
36
+ * via ONE syncPull (shadow-diff dedup). Returns { observed, deltasAppended } — a re-pull of
37
+ * identical state appends ZERO deltas (observed stays, deltasAppended drops to 0).
38
+ */
39
+ export declare function syncPineconeFromReal(rawClient: PineconeLikeClient, opts?: {
40
+ root?: string;
41
+ occurredAt?: string;
42
+ } & PineconeBudgetedOptions): Promise<{
43
+ observed: number;
44
+ deltasAppended: number;
45
+ }>;