pikelet-wasm 0.7.0 → 0.8.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 (32) hide show
  1. package/CHANGELOG.md +19 -3
  2. package/NOTICE +5 -0
  3. package/README.md +397 -454
  4. package/complete/builder.d.ts +4 -4
  5. package/complete/builder.mjs +8 -8
  6. package/complete/encoder-kernels/encoder.node.wasm +0 -0
  7. package/complete/encoder-kernels/encoder.wasm +0 -0
  8. package/complete/index.d.ts +11 -7
  9. package/complete/index.mjs +161 -112
  10. package/complete/inline-transformer.mjs +22 -9
  11. package/complete/lexical.mjs +11 -11
  12. package/complete/retrieval-abstention.mjs +18 -6
  13. package/complete/sources.mjs +22 -1
  14. package/dist/engine.js +1 -1
  15. package/dist/engine.scalar.js +1 -1
  16. package/dist/engine.scalar.wasm +0 -0
  17. package/dist/engine.wasm +0 -0
  18. package/package.json +58 -51
  19. package/{pancake-artifact-common.js → pikelet-artifact-common.js} +42 -42
  20. package/{pancake-artifact-range.js → pikelet-artifact-range.js} +36 -36
  21. package/{pancake-artifact-sketch.js → pikelet-artifact-sketch.js} +76 -66
  22. package/{pancake-artifact.d.ts → pikelet-artifact.d.ts} +11 -11
  23. package/{pancake-artifact.js → pikelet-artifact.js} +10 -10
  24. package/{pancake-core.js → pikelet-core.js} +136 -136
  25. package/{pancake-errors.js → pikelet-errors.js} +9 -8
  26. package/{pancake.d.ts → pikelet.d.ts} +55 -55
  27. package/{pancake.js → pikelet.js} +44 -44
  28. package/{pancake.node.mjs → pikelet.node.mjs} +46 -46
  29. package/{pancake.web.d.ts → pikelet.web.d.ts} +13 -13
  30. package/{pancake.web.mjs → pikelet.web.mjs} +20 -20
  31. package/{pancake.workerd.mjs → pikelet.workerd.mjs} +18 -18
  32. /package/{pancake-loader.js → pikelet-loader.js} +0 -0
@@ -1,4 +1,4 @@
1
- // The one-file reader: open a .pancake (spec/COMPLETE_PROFILE.md) and query
1
+ // The one-file reader: open a .pikelet (spec/COMPLETE_PROFILE.md) and query
2
2
  // it. Environment-neutral — the same module runs in Node (file path or
3
3
  // range source) and the browser (HTTP range source, via a bundler for the
4
4
  // CJS sketch-reader dependency). The query path needs no core HNSW engine:
@@ -8,15 +8,15 @@
8
8
  // engine's SIMD scan kernel (options.sketchScanner — auto-staged in Node,
9
9
  // injected in browsers), which only ever changes speed, never results.
10
10
  //
11
- // const search = await openPancakeFile('pancake-docs.pancake'); // Node
12
- // const search = await openPancakeFile(httpRangeSource(url)); // browser
11
+ // const search = await openPikeletFile('docs.pikelet'); // Node
12
+ // const search = await openPikeletFile(httpRangeSource(url)); // browser
13
13
  // const out = await search.query('how do workers restore snapshots');
14
14
  //
15
15
  // Every byte of the artifact is untrusted input (contract section 7): each
16
16
  // offset, length, and count is validated — safe integer, within the header's
17
17
  // fileBytes and the source's size, under the per-open read budget — before a
18
18
  // read is issued or a buffer allocated, and every read must return exactly
19
- // the bytes asked for. Format 2 files (profile pancake-complete-v2) carry
19
+ // the bytes asked for. Format 2 files (profile pikelet-complete-v2) carry
20
20
  // per-record corpus digests, so each hydrated record is verified on its own
21
21
  // range read; format 1 files verify eager segments whole and hydrate records
22
22
  // under the segment digest only (reported via info().corpusIntegrity).
@@ -25,7 +25,7 @@ import { loadStudentModel, embedTextWithStudent } from './student-embedder.mjs';
25
25
  import { computeMatchQuality, computePreSearchAbstention } from './student-abstention.mjs';
26
26
  import { createAbstentionScorer } from './retrieval-abstention.mjs';
27
27
  import { openLexicalIndex, openLexicalIndexLazy } from './lexical.mjs';
28
- import { PancakeSketchArtifact } from '../pancake-artifact.js';
28
+ import { PikeletSketchArtifact } from '../pikelet-artifact.js';
29
29
  import { MAGIC, HEADER_BYTES, TABLE_ENTRY_BYTES, KINDS, KIND_NAMES } from './format.mjs';
30
30
 
31
31
  import {
@@ -36,9 +36,11 @@ import {
36
36
  } from './inline-transformer.mjs';
37
37
 
38
38
  export { httpRangeSource } from './sources.mjs';
39
- // The project renamed to Pikelet (2026-09); the wire format and this
40
- // reader's original export keep their pancake-era names for
41
- // compatibility, and openPikeletFile is the documented alias.
39
+ // Re-exported for the builder side: calibration fits coverage against the
40
+ // same fused (vector+BM25) ranking the reader serves, which needs an
41
+ // in-memory lexical searcher over the segment bytes already assembled by
42
+ // buildLexicalSegment, before the artifact is written to disk.
43
+ export { openLexicalIndex } from './lexical.mjs';
42
44
  export {
43
45
  KERNEL_LAYOUT, expectedBlobBytes, parseInlineTransformerEncoder,
44
46
  createInlineTransformerEmbedder, INLINE_TEST_VECTOR_TEXTS,
@@ -47,7 +49,13 @@ export {
47
49
  };
48
50
 
49
51
  // Supported container formats: header formatVersion -> manifest profile.
50
- export const SUPPORTED_PROFILES = Object.freeze({ 1: 'pancake-complete-v1', 2: 'pancake-complete-v2' });
52
+ export const SUPPORTED_PROFILES = Object.freeze({ 1: 'pikelet-complete-v1', 2: 'pikelet-complete-v2' });
53
+ // Pre-rename profile strings (2026-09): every .pikelet published before this
54
+ // date — including live GitHub release assets — carries "pancake-complete-
55
+ // vN" in its manifest. Readers accept both spellings indefinitely so an
56
+ // already-published pack does not go dark on a reader upgrade; only
57
+ // SUPPORTED_PROFILES (above) is what a fresh build ever writes.
58
+ const LEGACY_PROFILES = Object.freeze({ 1: 'pancake-complete-v1', 2: 'pancake-complete-v2' });
51
59
  export const CORPUS_LAYOUT_V2 = 'records-v2';
52
60
 
53
61
  // Read budgets. Open-path reads (manifest, segment table, query-interp,
@@ -63,13 +71,20 @@ const MAX_RECORDS = 2 ** 31 - 1;
63
71
  const MAX_PAGE_RECORDS = 65536;
64
72
  const DIGEST_PAGE_CACHE = 64;
65
73
  const RECORD_CACHE = 256;
66
- // The embedded index is a .pancake-sketch artifact (SKETCH_PROFILE.md);
74
+ // The embedded index is a .pikelet-sketch artifact (SKETCH_PROFILE.md);
67
75
  // its fixed-size header is what format 2 commits to in the manifest.
68
76
  const SKETCH_HEADER_BYTES = 256;
69
77
  // Hybrid retrieval: BM25 candidates fetched from the lexical segment per
70
78
  // query, and the reciprocal-rank-fusion constant (the standard untuned 60).
71
79
  const LEXICAL_CANDIDATES = 24;
72
80
  const RRF_K = 60;
81
+ // Only BM25 hits within this fraction of the top lexical score join fusion.
82
+ // Common query terms give every matching document a near-tied score (idf
83
+ // collapses them flat); a wide cutoff let that tied mass ride into fusion on
84
+ // a technicality. Kept identical to pikelet/src/calibrate.mjs's
85
+ // LEXICAL_CUTOFF so calibration is fit against the same fusion the reader
86
+ // actually serves.
87
+ const LEXICAL_CUTOFF = 1.5;
73
88
  // Query-interpretation segments above this size open lazily (kind 3's
74
89
  // inline encoder is ~25 MiB); smaller ones keep the one-read eager path.
75
90
  const LAZY_QI_BYTES = 4 * 1024 * 1024;
@@ -123,7 +138,7 @@ function asUint8Array(bytes) {
123
138
  if (bytes instanceof Uint8Array) return bytes;
124
139
  if (bytes instanceof ArrayBuffer) return new Uint8Array(bytes);
125
140
  if (ArrayBuffer.isView(bytes)) return new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
126
- throw new Error('.pancake range source returned a non-binary value');
141
+ throw new Error('.pikelet range source returned a non-binary value');
127
142
  }
128
143
 
129
144
  function resolveBudget(value, label, fallback) {
@@ -138,7 +153,7 @@ function resolveBudget(value, label, fallback) {
138
153
  // u64 fields become JS numbers only when they are exactly representable.
139
154
  function u64(view, offset, label) {
140
155
  const big = view.getBigUint64(offset, true);
141
- if (big > MAX_SAFE_BIG) throw new Error(`.pancake ${label} exceeds the safe integer range`);
156
+ if (big > MAX_SAFE_BIG) throw new Error(`.pikelet ${label} exceeds the safe integer range`);
142
157
  return Number(big);
143
158
  }
144
159
 
@@ -148,16 +163,16 @@ function u64(view, offset, label) {
148
163
  function checkRange(source, offset, length, label, limit, fileBytes) {
149
164
  if (!Number.isSafeInteger(offset) || !Number.isSafeInteger(length)
150
165
  || offset < 0 || length < 0 || !Number.isSafeInteger(offset + length)) {
151
- throw new Error(`.pancake ${label} range is out of bounds`);
166
+ throw new Error(`.pikelet ${label} range is out of bounds`);
152
167
  }
153
168
  if (length > limit) {
154
- throw new Error(`.pancake ${label} read of ${length} bytes exceeds the ${limit}-byte read budget`);
169
+ throw new Error(`.pikelet ${label} read of ${length} bytes exceeds the ${limit}-byte read budget`);
155
170
  }
156
171
  if (fileBytes !== undefined && offset + length > fileBytes) {
157
- throw new Error(`.pancake ${label} range extends past the file (${offset}+${length} > ${fileBytes})`);
172
+ throw new Error(`.pikelet ${label} range extends past the file (${offset}+${length} > ${fileBytes})`);
158
173
  }
159
174
  if (Number.isSafeInteger(source.size) && offset + length > source.size) {
160
- throw new Error(`.pancake ${label} range extends past the source (${offset}+${length} > ${source.size})`);
175
+ throw new Error(`.pikelet ${label} range extends past the source (${offset}+${length} > ${source.size})`);
161
176
  }
162
177
  }
163
178
 
@@ -165,7 +180,7 @@ async function readChecked(source, offset, length, label, limit, fileBytes) {
165
180
  checkRange(source, offset, length, label, limit, fileBytes);
166
181
  const bytes = asUint8Array(await source.read(offset, length));
167
182
  if (bytes.length !== length) {
168
- throw new Error(`.pancake ${label} read returned ${bytes.length} of ${length} bytes (truncated or misbehaving source)`);
183
+ throw new Error(`.pikelet ${label} read returned ${bytes.length} of ${length} bytes (truncated or misbehaving source)`);
169
184
  }
170
185
  return bytes;
171
186
  }
@@ -185,7 +200,7 @@ async function fileSource(filePath) {
185
200
  preferredParallelism: Infinity,
186
201
  preferredGapBytes: 2048,
187
202
  async read(offset, length) {
188
- if (fd === null) throw new Error('.pancake file source is closed');
203
+ if (fd === null) throw new Error('.pikelet file source is closed');
189
204
  const buffer = new Uint8Array(length);
190
205
  let bytesRead = 0;
191
206
  while (bytesRead < length) {
@@ -217,7 +232,7 @@ function windowSource(source, offset, length) {
217
232
  preferredGapBytes: source.preferredGapBytes,
218
233
  async read(off, len) {
219
234
  if (!Number.isSafeInteger(off) || !Number.isSafeInteger(len) || off < 0 || len < 0 || off + len > length) {
220
- throw new Error('.pancake index segment read is outside the segment window');
235
+ throw new Error('.pikelet index segment read is outside the segment window');
221
236
  }
222
237
  return source.read(offset + off, len);
223
238
  },
@@ -257,19 +272,19 @@ function base64Bytes(text) {
257
272
  export async function verifyHostEncoder(declaration, encodeQuery, dim) {
258
273
  const vectors = declaration.testVectors;
259
274
  if (!Array.isArray(vectors) || vectors.length === 0) {
260
- throw new Error('.pancake kind-2 declaration carries no verification vectors');
275
+ throw new Error('.pikelet kind-2 declaration carries no verification vectors');
261
276
  }
262
277
  if (declaration.dim !== undefined && declaration.dim !== dim) {
263
- throw new Error(`.pancake kind-2 declaration dim ${declaration.dim} disagrees with manifest dim ${dim}`);
278
+ throw new Error(`.pikelet kind-2 declaration dim ${declaration.dim} disagrees with manifest dim ${dim}`);
264
279
  }
265
280
  for (const tv of vectors) {
266
281
  if (!tv || typeof tv.text !== 'string') {
267
- throw new Error('.pancake kind-2 verification vector is malformed');
282
+ throw new Error('.pikelet kind-2 verification vector is malformed');
268
283
  }
269
284
  // The expected embedding must be dim finite numbers: a malformed
270
285
  // entry would turn every comparison into NaN, and NaN > maxDiff is
271
286
  // false — the vector would "pass" without comparing anything.
272
- const label = `.pancake kind-2 verification vector "${tv.text.slice(0, 40)}…"`;
287
+ const label = `.pikelet kind-2 verification vector "${tv.text.slice(0, 40)}…"`;
273
288
  validateExpectedEmbedding(tv.embedding, dim, label);
274
289
  const tolerance = validateTestVectorTolerance(tv.tolerance, label);
275
290
  const vector = toFloat32(await encodeQuery(tv.text), dim, 'options.encodeQuery');
@@ -288,15 +303,15 @@ export async function verifyHostEncoder(declaration, encodeQuery, dim) {
288
303
  }
289
304
 
290
305
  /**
291
- * Open a .pancake complete artifact from a file path (Node) or a
306
+ * Open a .pikelet complete artifact from a file path (Node) or a
292
307
  * { read(offset, length), size? } range source (any runtime).
293
308
  * Returns { query(text, {k}), info(), evaluation(), close() }.
294
309
  */
295
- export async function openPancakeFile(input, options = {}) {
310
+ export async function openPikeletFile(input, options = {}) {
296
311
  const source = typeof input === 'string' ? await fileSource(input) : input;
297
312
  const owned = typeof input === 'string';
298
313
  if (!source || typeof source.read !== 'function') {
299
- throw new Error('openPancakeFile() requires a file path or a range source with read(offset, length)');
314
+ throw new Error('openPikeletFile() requires a file path or a range source with read(offset, length)');
300
315
  }
301
316
  const maxReadBytes = resolveBudget(options.maxReadBytes, 'maxReadBytes', DEFAULT_OPEN_READ_BYTES);
302
317
  const maxRecordBytes = resolveBudget(options.maxRecordBytes, 'maxRecordBytes', DEFAULT_RECORD_BYTES);
@@ -333,23 +348,23 @@ export async function openPancakeFile(input, options = {}) {
333
348
  const header = await readChecked(source, 0, HEADER_BYTES, 'header', maxReadBytes);
334
349
  const headerView = viewOf(header);
335
350
  if (headerView.getUint32(0, true) !== MAGIC) {
336
- throw new Error('not a .pancake file (bad magic)');
351
+ throw new Error('not a .pikelet file (bad magic)');
337
352
  }
338
353
  const formatVersion = headerView.getUint32(4, true);
339
354
  const profile = SUPPORTED_PROFILES[formatVersion];
340
- if (!profile) throw new Error(`unsupported .pancake format version ${formatVersion}`);
355
+ if (!profile) throw new Error(`unsupported .pikelet format version ${formatVersion}`);
341
356
  const manifestBytes = headerView.getUint32(8, true);
342
357
  const segmentCount = headerView.getUint32(12, true);
343
358
  const fileBytes = u64(headerView, 16, 'header fileBytes');
344
359
  if (manifestBytes < 2 || manifestBytes > MAX_MANIFEST_BYTES || segmentCount < 1 || segmentCount > MAX_SEGMENTS) {
345
- throw new Error('.pancake header is implausible');
360
+ throw new Error('.pikelet header is implausible');
346
361
  }
347
362
  if (Number.isSafeInteger(source.size) && source.size !== fileBytes) {
348
- throw new Error(`.pancake is truncated or padded: header says ${fileBytes} bytes, source has ${source.size}`);
363
+ throw new Error(`.pikelet is truncated or padded: header says ${fileBytes} bytes, source has ${source.size}`);
349
364
  }
350
365
  const tableBytes = segmentCount * TABLE_ENTRY_BYTES;
351
366
  if (HEADER_BYTES + manifestBytes + tableBytes > fileBytes) {
352
- throw new Error('.pancake header places the manifest or segment table past the file');
367
+ throw new Error('.pikelet header places the manifest or segment table past the file');
353
368
  }
354
369
  const identity = toHex(header.subarray(24, 56));
355
370
  // A caller pinning an expected identity (a shelf entry, a lockfile,
@@ -362,30 +377,31 @@ export async function openPancakeFile(input, options = {}) {
362
377
  throw new Error('options.expectedIdentity must be a sha256 hex string');
363
378
  }
364
379
  if (identity !== options.expectedIdentity.toLowerCase()) {
365
- throw new Error(`.pancake identity mismatch: expected ${options.expectedIdentity.toLowerCase()}, found ${identity}`);
380
+ throw new Error(`.pikelet identity mismatch: expected ${options.expectedIdentity.toLowerCase()}, found ${identity}`);
366
381
  }
367
382
  }
368
383
 
369
384
  const manifestBuf = await readChecked(source, HEADER_BYTES, manifestBytes, 'manifest', maxReadBytes, fileBytes);
370
385
  if (await sha256hex(manifestBuf) !== identity) {
371
- throw new Error('.pancake manifest failed identity verification');
386
+ throw new Error('.pikelet manifest failed identity verification');
372
387
  }
373
388
  let manifest;
374
389
  try { manifest = JSON.parse(decoder.decode(manifestBuf)); } catch (err) {
375
- throw new Error('.pancake manifest is not valid JSON', { cause: err });
390
+ throw new Error('.pikelet manifest is not valid JSON', { cause: err });
376
391
  }
377
- if (!manifest || typeof manifest !== 'object' || manifest.profile !== profile) {
392
+ if (!manifest || typeof manifest !== 'object'
393
+ || (manifest.profile !== profile && manifest.profile !== LEGACY_PROFILES[formatVersion])) {
378
394
  throw new Error(`unsupported profile ${manifest?.profile} for format version ${formatVersion}`);
379
395
  }
380
396
  if (!Array.isArray(manifest.segments) || manifest.segments.length !== segmentCount) {
381
- throw new Error('.pancake manifest segment list disagrees with the header segment count');
397
+ throw new Error('.pikelet manifest segment list disagrees with the header segment count');
382
398
  }
383
399
  const dim = manifest.dim;
384
400
  if (!Number.isSafeInteger(dim) || dim < 1 || dim > 65536) {
385
- throw new Error('.pancake manifest dim is implausible');
401
+ throw new Error('.pikelet manifest dim is implausible');
386
402
  }
387
403
  if (!manifest.corpus || typeof manifest.corpus !== 'object') {
388
- throw new Error('.pancake manifest has no corpus block');
404
+ throw new Error('.pikelet manifest has no corpus block');
389
405
  }
390
406
 
391
407
  const table = await readChecked(source, HEADER_BYTES + manifestBytes, tableBytes, 'segment table', maxReadBytes, fileBytes);
@@ -401,26 +417,26 @@ export async function openPancakeFile(input, options = {}) {
401
417
  const declared = manifest.segments[i];
402
418
  if (!declared || typeof declared !== 'object' || declared.bytes !== length || !isSha256Hex(declared.sha256)
403
419
  || offset !== expectedOffset || !Number.isSafeInteger(offset + length) || offset + length > fileBytes) {
404
- throw new Error(`.pancake segment table disagrees with manifest at entry ${i}`);
420
+ throw new Error(`.pikelet segment table disagrees with manifest at entry ${i}`);
405
421
  }
406
422
  if (kind === undefined) {
407
423
  // Unknown kinds are skipped, not failed (spec 3.3); they are
408
424
  // still committed through the manifest, so the declared name
409
425
  // must at least not claim to be a kind this reader knows.
410
426
  if (typeof declared.kind !== 'string' || declared.kind in KINDS) {
411
- throw new Error(`.pancake segment table entry ${i} has unknown kind ${kindNumber} but the manifest names it ${declared.kind}`);
427
+ throw new Error(`.pikelet segment table entry ${i} has unknown kind ${kindNumber} but the manifest names it ${declared.kind}`);
412
428
  }
413
429
  } else {
414
430
  if (declared.kind !== kind) {
415
- throw new Error(`.pancake segment table disagrees with manifest at entry ${i}`);
431
+ throw new Error(`.pikelet segment table disagrees with manifest at entry ${i}`);
416
432
  }
417
- if (segments.has(kind)) throw new Error(`.pancake carries more than one ${kind} segment`);
433
+ if (segments.has(kind)) throw new Error(`.pikelet carries more than one ${kind} segment`);
418
434
  segments.set(kind, { offset, length, sha256: declared.sha256 });
419
435
  }
420
436
  expectedOffset = align16(offset + length);
421
437
  }
422
438
  for (const required of ['index', 'corpus', 'query-interp']) {
423
- if (!segments.has(required)) throw new Error(`.pancake is missing the ${required} segment`);
439
+ if (!segments.has(required)) throw new Error(`.pikelet is missing the ${required} segment`);
424
440
  }
425
441
 
426
442
  // Corpus tables: count + offsets (+ page table for layout v2),
@@ -432,15 +448,15 @@ export async function openPancakeFile(input, options = {}) {
432
448
  const corpus = segments.get('corpus');
433
449
  const declaredRecords = manifest.corpus.records;
434
450
  if (!Number.isSafeInteger(declaredRecords) || declaredRecords < 0 || declaredRecords > MAX_RECORDS) {
435
- throw new Error('.pancake manifest corpus.records is implausible');
451
+ throw new Error('.pikelet manifest corpus.records is implausible');
436
452
  }
437
453
  const layout = manifest.corpus.layout;
438
454
  const perRecord = formatVersion >= 2;
439
455
  if (perRecord && layout !== CORPUS_LAYOUT_V2) {
440
- throw new Error(`.pancake format ${formatVersion} requires corpus layout ${CORPUS_LAYOUT_V2}, manifest says ${layout}`);
456
+ throw new Error(`.pikelet format ${formatVersion} requires corpus layout ${CORPUS_LAYOUT_V2}, manifest says ${layout}`);
441
457
  }
442
458
  if (!perRecord && layout !== undefined) {
443
- throw new Error(`.pancake format 1 carries corpus layout v1, manifest says ${layout}`);
459
+ throw new Error(`.pikelet format 1 carries corpus layout v1, manifest says ${layout}`);
444
460
  }
445
461
  let pageRecords = 0;
446
462
  let pages = 0;
@@ -451,12 +467,12 @@ export async function openPancakeFile(input, options = {}) {
451
467
  if (perRecord) {
452
468
  pageRecords = manifest.corpus.pageRecords;
453
469
  if (!Number.isSafeInteger(pageRecords) || pageRecords < 1 || pageRecords > MAX_PAGE_RECORDS) {
454
- throw new Error('.pancake manifest corpus.pageRecords is implausible');
470
+ throw new Error('.pikelet manifest corpus.pageRecords is implausible');
455
471
  }
456
472
  pages = Math.ceil(declaredRecords / pageRecords);
457
473
  if (manifest.corpus.pages !== pages || !isSha256Hex(manifest.corpus.pageTableSha256)
458
474
  || manifest.corpus.recordDigest !== 'sha256') {
459
- throw new Error('.pancake manifest corpus integrity block is inconsistent');
475
+ throw new Error('.pikelet manifest corpus integrity block is inconsistent');
460
476
  }
461
477
  offsetsAt = 8;
462
478
  tablesBytes = offsetsAt + 8 * (declaredRecords + 1) + 32 * pages;
@@ -468,7 +484,7 @@ export async function openPancakeFile(input, options = {}) {
468
484
  recordsAt = tablesBytes;
469
485
  }
470
486
  if (!Number.isSafeInteger(recordsAt) || recordsAt > corpus.length) {
471
- throw new Error('.pancake manifest corpus.records is implausible for the corpus segment');
487
+ throw new Error('.pikelet manifest corpus.records is implausible for the corpus segment');
472
488
  }
473
489
  // Format 2 commits to the embedded sketch's 256-byte header
474
490
  // (metric/dim/count and the sketch's residentSha256/vectorsSha256),
@@ -476,7 +492,7 @@ export async function openPancakeFile(input, options = {}) {
476
492
  // sketch's self-checks alone verify against fields an attacker who
477
493
  // rewrites the segment also controls.
478
494
  if (perRecord && (!isSha256Hex(manifest.index?.headerSha256) || idx.length < SKETCH_HEADER_BYTES)) {
479
- throw new Error('.pancake manifest carries no index header commitment (index.headerSha256)');
495
+ throw new Error('.pikelet manifest carries no index header commitment (index.headerSha256)');
480
496
  }
481
497
 
482
498
  // After the segment table, every remaining open-path extent is known,
@@ -502,11 +518,11 @@ export async function openPancakeFile(input, options = {}) {
502
518
  }
503
519
  const bytes = await readChecked(source, qi.offset, qi.length, 'query-interp segment', maxReadBytes, fileBytes);
504
520
  if (await sha256hex(bytes) !== qi.sha256) {
505
- throw new Error('.pancake query-interp segment failed hash verification');
521
+ throw new Error('.pikelet query-interp segment failed hash verification');
506
522
  }
507
523
  return bytes;
508
524
  })(),
509
- PancakeSketchArtifact.open(windowSource(source, idx.offset, idx.length), { maxReadBytes }),
525
+ PikeletSketchArtifact.open(windowSource(source, idx.offset, idx.length), { maxReadBytes }),
510
526
  readChecked(source, corpus.offset, tablesBytes, 'corpus tables', maxReadBytes, fileBytes),
511
527
  // Lexical index (kind 5, OPTIONAL). Small segments: eager
512
528
  // whole-segment read, digest-verified like the query-interp
@@ -522,14 +538,14 @@ export async function openPancakeFile(input, options = {}) {
522
538
  }
523
539
  const bytes = await readChecked(source, seg.offset, seg.length, 'lexical segment', maxReadBytes, fileBytes);
524
540
  if (await sha256hex(bytes) !== seg.sha256) {
525
- throw new Error('.pancake lexical segment failed hash verification');
541
+ throw new Error('.pikelet lexical segment failed hash verification');
526
542
  }
527
543
  return { bytes };
528
544
  })() : null,
529
545
  perRecord ? (async () => {
530
546
  const headerBytes = await readChecked(source, idx.offset, SKETCH_HEADER_BYTES, 'index header', maxReadBytes, fileBytes);
531
547
  if (await sha256hex(headerBytes) !== manifest.index.headerSha256) {
532
- throw new Error('.pancake index header failed hash verification against the manifest');
548
+ throw new Error('.pikelet index header failed hash verification against the manifest');
533
549
  }
534
550
  })() : null,
535
551
  ]);
@@ -538,12 +554,12 @@ export async function openPancakeFile(input, options = {}) {
538
554
  ? (lexicalBytes.lazyIndex || openLexicalIndex(lexicalBytes.bytes))
539
555
  : null;
540
556
  if (lexicalIndex && lexicalIndex.docCount !== declaredRecords) {
541
- throw new Error(`.pancake lexical segment covers ${lexicalIndex.docCount} records, corpus declares ${declaredRecords}`);
557
+ throw new Error(`.pikelet lexical segment covers ${lexicalIndex.docCount} records, corpus declares ${declaredRecords}`);
542
558
  }
543
559
 
544
560
  // Query interpretation: version word, kind, two length-prefixed
545
561
  // regions that must tile the segment exactly.
546
- if (qi.length < 16) throw new Error('.pancake query-interp segment is too short');
562
+ if (qi.length < 16) throw new Error('.pikelet query-interp segment is too short');
547
563
  const qiView = viewOf(qiFirst);
548
564
  const qiVersion = qiView.getUint32(0, true);
549
565
  if (qiVersion !== 1) throw new Error(`unsupported query-interpretation version ${qiVersion}`);
@@ -551,7 +567,7 @@ export async function openPancakeFile(input, options = {}) {
551
567
  const encoderLen = qiView.getUint32(8, true);
552
568
  const calibrationLen = qiView.getUint32(12, true);
553
569
  if (16 + encoderLen + calibrationLen !== qi.length) {
554
- throw new Error('.pancake query-interp segment layout is inconsistent');
570
+ throw new Error('.pikelet query-interp segment layout is inconsistent');
555
571
  }
556
572
  // Laziness only pays for kind 3 (the deferrable bulk IS the
557
573
  // encoder); a large segment of any other kind falls back to the
@@ -561,7 +577,7 @@ export async function openPancakeFile(input, options = {}) {
561
577
  if (qiDeferrable && qiKind !== 3) {
562
578
  qiBytes = await readChecked(source, qi.offset, qi.length, 'query-interp segment', maxReadBytes, fileBytes);
563
579
  if (await sha256hex(qiBytes) !== qi.sha256) {
564
- throw new Error('.pancake query-interp segment failed hash verification');
580
+ throw new Error('.pikelet query-interp segment failed hash verification');
565
581
  }
566
582
  qiDeferred = false;
567
583
  }
@@ -574,10 +590,10 @@ export async function openPancakeFile(input, options = {}) {
574
590
  const encoderBytes = qiDeferred ? null : qiBytes.subarray(16, 16 + encoderLen);
575
591
  let calibrationJson;
576
592
  try { calibrationJson = JSON.parse(decoder.decode(calibrationRegion)); } catch (err) {
577
- throw new Error('.pancake calibration is not valid JSON', { cause: err });
593
+ throw new Error('.pikelet calibration is not valid JSON', { cause: err });
578
594
  }
579
595
  if (!calibrationJson || typeof calibrationJson !== 'object') {
580
- throw new Error('.pancake calibration must be a JSON object');
596
+ throw new Error('.pikelet calibration must be a JSON object');
581
597
  }
582
598
 
583
599
  // kind 1 (student-inline-v1): pure-JS inline encoder + feature-stream
@@ -595,16 +611,25 @@ export async function openPancakeFile(input, options = {}) {
595
611
  const retrievalScorer = () => {
596
612
  const scorer = createAbstentionScorer(calibrationJson.asset, base64Bytes(calibrationJson.vocabBloomBase64));
597
613
  const VERDICTS = { answer: 'strong', weak: 'weak', abstain: 'none' };
598
- return async (hits, context) => {
614
+ return async (hits, context, fusedHits) => {
599
615
  if (!scorer) return { match_quality: 'unscored' };
600
616
  // The coverage term grounds the verdict in the top passages'
601
- // text, so those records hydrate before scoring. When the
602
- // query is answered the pages are already hot for result
603
- // hydration; an abstained query costs these extra reads.
604
- // Hydration failures propagate — on format 2 a record that
605
- // fails its digest must fail the query, not skew its verdict.
606
- const topTexts = scorer.usesPassage && hits.length
607
- ? (await Promise.all(hits.slice(0, scorer.passagesNeeded || 1)
617
+ // text, so those records hydrate before scoring. Hydrated
618
+ // from the fused (RRF) order when hybrid retrieval produced
619
+ // one — calibrate.mjs fits coverage against the same fused
620
+ // ranking, and a chunk BM25 ranks first but the embedding
621
+ // ranks far down is exactly the case coverage exists to
622
+ // catch; scoring it against vector-only order would miss it
623
+ // again. Falls back to vector order (hits) when there is no
624
+ // lexical segment or fusion did not change anything. When
625
+ // the query is answered the pages are already hot for
626
+ // result hydration; an abstained query costs these extra
627
+ // reads. Hydration failures propagate — on format 2 a
628
+ // record that fails its digest must fail the query, not
629
+ // skew its verdict.
630
+ const passageSource = fusedHits?.length ? fusedHits : hits;
631
+ const topTexts = scorer.usesPassage && passageSource.length
632
+ ? (await Promise.all(passageSource.slice(0, scorer.passagesNeeded || 1)
608
633
  .map((hit) => hydrate(hit.id)))).map((record) => record?.text)
609
634
  : [];
610
635
  const scored = scorer.score(context.text, hits, topTexts);
@@ -625,9 +650,9 @@ export async function openPancakeFile(input, options = {}) {
625
650
  } else if (qiKind === 2) {
626
651
  let declaration;
627
652
  try { declaration = JSON.parse(decoder.decode(encoderBytes)); } catch (err) {
628
- throw new Error('.pancake kind-2 encoder declaration is not valid JSON', { cause: err });
653
+ throw new Error('.pikelet kind-2 encoder declaration is not valid JSON', { cause: err });
629
654
  }
630
- if (!declaration || typeof declaration !== 'object') throw new Error('.pancake kind-2 encoder declaration must be a JSON object');
655
+ if (!declaration || typeof declaration !== 'object') throw new Error('.pikelet kind-2 encoder declaration must be a JSON object');
631
656
  encoderInfo = declaration;
632
657
  const encodeQuery = options.encodeQuery;
633
658
  if (encodeQuery !== undefined && typeof encodeQuery !== 'function') {
@@ -641,7 +666,7 @@ export async function openPancakeFile(input, options = {}) {
641
666
  } else if (options.allowUnverifiedEncoder === true) {
642
667
  encoderVerified = false;
643
668
  } else {
644
- throw new Error(`.pancake declares an external encoder (${declaration.model}) without verification vectors, `
669
+ throw new Error(`.pikelet declares an external encoder (${declaration.model}) without verification vectors, `
645
670
  + 'so the host encoder cannot be checked against it (contract section 4.4); '
646
671
  + 'pass options.allowUnverifiedEncoder: true to serve it anyway, marked unverified');
647
672
  }
@@ -650,8 +675,8 @@ export async function openPancakeFile(input, options = {}) {
650
675
  }
651
676
  embed = async (text) => {
652
677
  if (!encodeQuery) {
653
- throw new Error(`.pancake declares an external encoder (${declaration.model}); `
654
- + 'pass options.encodeQuery to openPancakeFile');
678
+ throw new Error(`.pikelet declares an external encoder (${declaration.model}); `
679
+ + 'pass options.encodeQuery to openPikeletFile');
655
680
  }
656
681
  return { vector: toFloat32(await encodeQuery(text), dim, 'options.encodeQuery'), text };
657
682
  };
@@ -680,7 +705,7 @@ export async function openPancakeFile(input, options = {}) {
680
705
  if (qiDeferred) {
681
706
  const full = await readChecked(source, qi.offset, qi.length, 'query-interp segment', maxReadBytes, fileBytes);
682
707
  if (await sha256hex(full) !== qi.sha256) {
683
- throw new Error('.pancake query-interp segment failed hash verification');
708
+ throw new Error('.pikelet query-interp segment failed hash verification');
684
709
  }
685
710
  // The header and calibration slices used at open were
686
711
  // unverified; the digest-verified segment must contain
@@ -688,13 +713,13 @@ export async function openPancakeFile(input, options = {}) {
688
713
  // every query must fail.
689
714
  if (!bytesEqual(full.subarray(0, 16), openHeader)
690
715
  || !bytesEqual(full.subarray(16 + encoderLen), calibrationRegion)) {
691
- throw new Error('.pancake query-interp segment bytes changed between open and encoder fetch');
716
+ throw new Error('.pikelet query-interp segment bytes changed between open and encoder fetch');
692
717
  }
693
718
  encBytes = full.subarray(16, 16 + encoderLen);
694
719
  }
695
720
  const { declaration, vocabText, blob } = parseInlineTransformerEncoder(encBytes);
696
721
  if (declaration.dim !== undefined && declaration.dim !== dim) {
697
- throw new Error(`.pancake kind-3 declaration dim ${declaration.dim} disagrees with manifest dim ${dim}`);
722
+ throw new Error(`.pikelet kind-3 declaration dim ${declaration.dim} disagrees with manifest dim ${dim}`);
698
723
  }
699
724
  const embedder = await createInlineTransformerEmbedder({ declaration, vocabText, blob, createEncoder, verify: verifyEncoder });
700
725
  encoderInfo = declaration;
@@ -732,10 +757,10 @@ export async function openPancakeFile(input, options = {}) {
732
757
  // Index: the embedded sketch artifact, opened in the wave above
733
758
  // against a segment-windowed source (resident hash verified there).
734
759
  if (sketch.count !== declaredRecords) {
735
- throw new Error(`.pancake index count ${sketch.count} != corpus records ${declaredRecords}`);
760
+ throw new Error(`.pikelet index count ${sketch.count} != corpus records ${declaredRecords}`);
736
761
  }
737
762
  if (sketch.dim !== undefined && sketch.dim !== dim) {
738
- throw new Error(`.pancake index dim ${sketch.dim} != manifest dim ${dim}`);
763
+ throw new Error(`.pikelet index dim ${sketch.dim} != manifest dim ${dim}`);
739
764
  }
740
765
  // The metric decides the search semantics; the sketch header's copy
741
766
  // must agree with the identity-verified manifest (on format 2 the
@@ -743,7 +768,7 @@ export async function openPancakeFile(input, options = {}) {
743
768
  // is the only binding).
744
769
  if (METRIC_NAMES[sketch.metric] !== undefined && typeof manifest.metric === 'string'
745
770
  && METRIC_NAMES[sketch.metric] !== manifest.metric) {
746
- throw new Error(`.pancake index metric ${METRIC_NAMES[sketch.metric]} != manifest metric ${manifest.metric}`);
771
+ throw new Error(`.pikelet index metric ${METRIC_NAMES[sketch.metric]} != manifest metric ${manifest.metric}`);
747
772
  }
748
773
  if (verifyIndexVectors) {
749
774
  await sketch.verifyVectors();
@@ -788,9 +813,9 @@ export async function openPancakeFile(input, options = {}) {
788
813
  // Hidden from bundlers like the node encoder kernels: the
789
814
  // web entrypoint's ?url wasm imports would break a browser
790
815
  // build that statically pulled this in.
791
- const { default: Pancake } = await import(/* webpackIgnore: true */ /* @vite-ignore */ '../pancake.node.mjs');
816
+ const { default: Pikelet } = await import(/* webpackIgnore: true */ /* @vite-ignore */ '../pikelet.node.mjs');
792
817
  await sketch.fullyResident;
793
- const created = await Pancake.createSketchScanner(sketch, {
818
+ const created = await Pikelet.createSketchScanner(sketch, {
794
819
  maxRerank: Math.min(sketch.count, SCANNER_MAX_RERANK),
795
820
  });
796
821
  disposeScanner = () => created.dispose();
@@ -805,16 +830,16 @@ export async function openPancakeFile(input, options = {}) {
805
830
  const tablesView = viewOf(tables);
806
831
  const recordCount = tablesView.getUint32(0, true);
807
832
  if (recordCount !== declaredRecords) {
808
- throw new Error('.pancake corpus count disagrees with manifest');
833
+ throw new Error('.pikelet corpus count disagrees with manifest');
809
834
  }
810
835
  if (perRecord && tablesView.getUint32(4, true) !== pageRecords) {
811
- throw new Error('.pancake corpus page size disagrees with manifest');
836
+ throw new Error('.pikelet corpus page size disagrees with manifest');
812
837
  }
813
838
  const recordOffsets = new Float64Array(recordCount + 1);
814
839
  for (let i = 0; i <= recordCount; i++) {
815
840
  const value = u64(tablesView, offsetsAt + 8 * i, `corpus offset ${i}`);
816
841
  if (value > corpus.length || (i > 0 && value < recordOffsets[i - 1]) || (i === 0 && value !== recordsAt)) {
817
- throw new Error('.pancake corpus offsets are inconsistent');
842
+ throw new Error('.pikelet corpus offsets are inconsistent');
818
843
  }
819
844
  recordOffsets[i] = value;
820
845
  }
@@ -822,7 +847,7 @@ export async function openPancakeFile(input, options = {}) {
822
847
  if (perRecord) {
823
848
  pageTable = tables.slice(offsetsAt + 8 * (recordCount + 1), tablesBytes);
824
849
  if (await sha256hex(pageTable) !== manifest.corpus.pageTableSha256) {
825
- throw new Error('.pancake corpus page table failed hash verification');
850
+ throw new Error('.pikelet corpus page table failed hash verification');
826
851
  }
827
852
  }
828
853
  const corpusIntegrity = perRecord ? (verifyRecords ? 'per-record-sha256' : 'per-record-sha256 (unverified by option)') : 'segment-sha256';
@@ -846,7 +871,7 @@ export async function openPancakeFile(input, options = {}) {
846
871
  `corpus digest page ${page}`, maxReadBytes, fileBytes);
847
872
  if (await sha256hex(bytes) !== toHex(pageTable.subarray(32 * page, 32 * page + 32))) {
848
873
  digestPages.delete(page);
849
- throw new Error(`.pancake corpus digest page ${page} failed hash verification`);
874
+ throw new Error(`.pikelet corpus digest page ${page} failed hash verification`);
850
875
  }
851
876
  return bytes;
852
877
  })();
@@ -869,7 +894,7 @@ export async function openPancakeFile(input, options = {}) {
869
894
 
870
895
  const hydrate = async (id) => {
871
896
  if (!Number.isSafeInteger(id) || id < 0 || id >= recordCount) {
872
- throw new Error(`.pancake result id ${id} is outside the corpus`);
897
+ throw new Error(`.pikelet result id ${id} is outside the corpus`);
873
898
  }
874
899
  if (recordCache.has(id)) {
875
900
  const cached = recordCache.get(id);
@@ -885,15 +910,15 @@ export async function openPancakeFile(input, options = {}) {
885
910
  const digests = await pageDigests(page);
886
911
  const at = 32 * (id - page * pageRecords);
887
912
  if (await sha256hex(bytes) !== toHex(digests.subarray(at, at + 32))) {
888
- throw new Error(`.pancake corpus record ${id} failed integrity verification`);
913
+ throw new Error(`.pikelet corpus record ${id} failed integrity verification`);
889
914
  }
890
915
  }
891
916
  let record;
892
917
  try { record = JSON.parse(decoder.decode(bytes)); } catch (err) {
893
- throw new Error(`.pancake corpus record ${id} is not valid JSON`, { cause: err });
918
+ throw new Error(`.pikelet corpus record ${id} is not valid JSON`, { cause: err });
894
919
  }
895
920
  if (!record || typeof record !== 'object' || Array.isArray(record)) {
896
- throw new Error(`.pancake corpus record ${id} is not a JSON object`);
921
+ throw new Error(`.pikelet corpus record ${id} is not a JSON object`);
897
922
  }
898
923
  return cacheRecord(id, record);
899
924
  };
@@ -903,7 +928,7 @@ export async function openPancakeFile(input, options = {}) {
903
928
  // use after close instead of reading through a freed encoder.
904
929
  let closed = false;
905
930
  const assertOpen = () => {
906
- if (closed) throw new Error('.pancake reader is closed');
931
+ if (closed) throw new Error('.pikelet reader is closed');
907
932
  };
908
933
 
909
934
  return {
@@ -976,6 +1001,8 @@ export async function openPancakeFile(input, options = {}) {
976
1001
  const pre = preScore(context);
977
1002
  let hits = [];
978
1003
  let fused = null;
1004
+ let searched = null;
1005
+ let fusedFull = null;
979
1006
  if (!pre) {
980
1007
  // Hybrid retrieval when the artifact carries a lexical
981
1008
  // segment: the BM25 top matches join the sketch's exact
@@ -1012,8 +1039,8 @@ export async function openPancakeFile(input, options = {}) {
1012
1039
  }
1013
1040
  const lexicalRaw = lexicalIndex && retrieval !== 'vector'
1014
1041
  ? await lexicalIndex.search(trimmed, LEXICAL_CANDIDATES) : [];
1015
- const lexicalHits = lexicalRaw.filter((h) => h.score >= lexicalRaw[0].score / 3);
1016
- const searched = (await sketch.search(context.vector, k, {
1042
+ const lexicalHits = lexicalRaw.filter((h) => h.score >= lexicalRaw[0].score / LEXICAL_CUTOFF);
1043
+ searched = (await sketch.search(context.vector, k, {
1017
1044
  rerank: queryOptions.rerank,
1018
1045
  parallelism: queryOptions.parallelism ?? queryOptions.rerankParallelism ?? options.rerankParallelism,
1019
1046
  // The identity-verified manifest may carry a fetch
@@ -1029,31 +1056,56 @@ export async function openPancakeFile(input, options = {}) {
1029
1056
  // sketch reader ignores it for any query whose C
1030
1057
  // exceeds the scanner's buffers.
1031
1058
  ...(scanner ? { scanner } : {}),
1032
- ...(lexicalHits.length ? {
1033
- extraCandidates: lexicalHits.map((h) => h.id),
1034
- fullRerankOutput: true,
1035
- } : {}),
1059
+ ...(lexicalHits.length ? { extraCandidates: lexicalHits.map((h) => h.id) } : {}),
1060
+ // Always return the full reranked candidate list
1061
+ // (already fetched and exactly scored) rather than
1062
+ // truncating to k: abstention is fit at a fixed
1063
+ // top-10 window (calibrate.mjs) independent of the
1064
+ // caller's k, and needs that full window to score
1065
+ // consistently — see retrieval-abstention.mjs.
1066
+ fullRerankOutput: true,
1036
1067
  })).results;
1037
1068
  hits = searched.slice(0, k);
1038
- if (retrieval === 'augmented') {
1039
- fused = null; // distance order, candidates already augmented
1040
- } else if (retrieval === 'lexical') {
1069
+ // Full-window fused order (not k-truncated) for scoring:
1070
+ // coverage grounds its verdict in the passage fusion
1071
+ // actually ranks first, and calibrate.mjs fits it the
1072
+ // same way — a k-truncated fused list would silently
1073
+ // drop the fused top hit for callers requesting a small k.
1074
+ if (retrieval === 'lexical') {
1041
1075
  const byId = new Map(searched.map((hit) => [hit.id, hit]));
1042
- fused = lexicalHits.map((h) => byId.get(h.id)).filter(Boolean).slice(0, k);
1043
- } else if (lexicalHits.length) {
1076
+ fusedFull = lexicalHits.map((h) => byId.get(h.id)).filter(Boolean);
1077
+ } else if (retrieval !== 'augmented' && lexicalHits.length) {
1044
1078
  const lexRank = new Map(lexicalHits.map((h, i) => [h.id, i]));
1045
- fused = searched
1079
+ fusedFull = searched
1046
1080
  .map((hit, vRank) => ({
1047
1081
  hit,
1048
1082
  score: 1 / (RRF_K + vRank) + (lexRank.has(hit.id) ? 1 / (RRF_K + lexRank.get(hit.id)) : 0),
1049
1083
  }))
1050
1084
  .sort((a, b) => (b.score - a.score) || (a.hit.distance - b.hit.distance))
1051
- .slice(0, k)
1052
1085
  .map((entry) => entry.hit);
1053
1086
  }
1087
+ fused = retrieval === 'augmented' ? null : fusedFull?.slice(0, k) ?? null;
1054
1088
  }
1055
- const quality = pre || await scoreQuality(hits, context);
1056
- const returned = quality.match_quality === 'none' ? [] : (fused || hits);
1089
+ // Abstention scores the full reranked candidate window
1090
+ // (searched), not the k-truncated hits: calibrate.mjs fits
1091
+ // its signals at a fixed top-10 (top-5 for coverage)
1092
+ // regardless of how many results a caller ultimately
1093
+ // requests, so scoring must use the same window to match.
1094
+ // preScore (kind 1) runs before search and has no window to
1095
+ // fix, so it is unaffected. fusedFull (RRF over the same
1096
+ // full window, not k-truncated) is threaded through
1097
+ // separately so coverage grades the passage fusion actually
1098
+ // ranks first, while d0/margin/mean10 still read vector
1099
+ // order (searched) exactly as calibrated.
1100
+ const quality = pre || await scoreQuality(searched ?? hits, context, fusedFull);
1101
+ // A 'none' verdict withholds results by default — the
1102
+ // calibrated abstention signal is doing its job, and most
1103
+ // callers want that. showAbstained is an explicit opt-out
1104
+ // for a caller that wants the raw retrieval anyway (e.g. to
1105
+ // inspect why the classifier abstained); it never changes
1106
+ // matchQuality or confidence, only whether results ships.
1107
+ const returned = quality.match_quality === 'none' && !queryOptions.showAbstained
1108
+ ? [] : (fused || hits);
1057
1109
  // The search's id and distance are authoritative: they are
1058
1110
  // written last so a corpus record carrying its own `id` or
1059
1111
  // `distance` field cannot overwrite them (reserved names).
@@ -1087,14 +1139,14 @@ export async function openPancakeFile(input, options = {}) {
1087
1139
  if (!ev) return null;
1088
1140
  const bytes = await readChecked(source, ev.offset, ev.length, 'evaluation segment', maxReadBytes, fileBytes);
1089
1141
  if (await sha256hex(bytes) !== ev.sha256) {
1090
- throw new Error('.pancake evaluation segment failed hash verification');
1142
+ throw new Error('.pikelet evaluation segment failed hash verification');
1091
1143
  }
1092
1144
  let parsed;
1093
1145
  try { parsed = JSON.parse(decoder.decode(bytes)); } catch (err) {
1094
- throw new Error('.pancake evaluation segment is not valid JSON', { cause: err });
1146
+ throw new Error('.pikelet evaluation segment is not valid JSON', { cause: err });
1095
1147
  }
1096
1148
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
1097
- throw new Error('.pancake evaluation segment must be a JSON object');
1149
+ throw new Error('.pikelet evaluation segment must be a JSON object');
1098
1150
  }
1099
1151
  return parsed;
1100
1152
  },
@@ -1119,6 +1171,3 @@ export async function openPancakeFile(input, options = {}) {
1119
1171
  throw err;
1120
1172
  }
1121
1173
  }
1122
-
1123
- // Documented alias (see the rename note at the top of this file).
1124
- export { openPancakeFile as openPikeletFile };