@fortemi/core 2026.7.2 → 2026.7.3

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.
@@ -1,4 +1,4 @@
1
- type AiwgFortemiKnownRecordType = 'crm.contact' | 'crm.organization' | 'crm.event' | 'crm.interaction' | 'aiwg.artifact' | 'docs.page';
1
+ type AiwgFortemiKnownRecordType = 'crm.contact' | 'crm.organization' | 'crm.event' | 'crm.interaction' | 'aiwg.artifact' | 'aiwg.kb.page';
2
2
  type AiwgFortemiRecordType = AiwgFortemiKnownRecordType | `aiwg.${string}` | `research.${string}` | `docs.${string}` | string;
3
3
  type AiwgFortemiRecordSchemaVersion = 'aiwg.fortemi.index.record.v1' | 'aiwg.fortemi.index.record.v2';
4
4
  type AiwgFortemiIndexExportSchemaVersion = 'aiwg.fortemi.index.export.v1' | 'aiwg.fortemi.index.export.v2';
@@ -141,10 +141,7 @@ interface AiwgFortemiIndexExport {
141
141
  source: {
142
142
  repo: string;
143
143
  privacy: AiwgPrivacyClassification;
144
- origin?: string;
145
- generated?: boolean;
146
- checksum?: string;
147
- updated_at?: string;
144
+ graph?: Record<string, unknown>;
148
145
  };
149
146
  items: AiwgFortemiRecord[];
150
147
  compatibility?: Record<string, unknown>;
@@ -165,6 +162,7 @@ interface AiwgFortemiChunkManifest {
165
162
  schema_version: 'aiwg.fortemi.index.chunk-manifest.v1';
166
163
  generated_at: string;
167
164
  source: AiwgFortemiIndexExport['source'];
165
+ source_export_schema_version?: AiwgFortemiIndexExportSchemaVersion;
168
166
  total: number;
169
167
  part_size: number;
170
168
  facets?: Record<string, Record<string, number>>;
@@ -338,6 +336,14 @@ interface AiwgHeadlessEmbeddingBackend {
338
336
  dimensions: number;
339
337
  embed(input: string, record: AiwgFortemiRecord): number[] | Promise<number[]>;
340
338
  }
339
+ interface AiwgPrivacyFilterOptions {
340
+ /** Include records classified `private` (default false). */
341
+ includePrivate?: boolean;
342
+ /** Include records flagged `pii` (default false). */
343
+ includePii?: boolean;
344
+ }
345
+ /** Drop `private`/`pii` records unless explicitly opted in (SEC6, default-safe). */
346
+ declare function filterAiwgRecordsByPrivacy(records: AiwgFortemiRecord[], options?: AiwgPrivacyFilterOptions): AiwgFortemiRecord[];
341
347
  interface BuildAiwgStaticEmbeddingSetOptions {
342
348
  id: string;
343
349
  backend: AiwgHeadlessEmbeddingBackend;
@@ -346,6 +352,8 @@ interface BuildAiwgStaticEmbeddingSetOptions {
346
352
  granularity?: AiwgStaticEmbeddingSet['granularity'];
347
353
  metric?: AiwgStaticEmbeddingSet['metric'];
348
354
  textForRecord?: (record: AiwgFortemiRecord) => string;
355
+ /** Privacy filtering (SEC6). Default-safe: excludes `private`/`pii` records. */
356
+ privacy?: AiwgPrivacyFilterOptions;
349
357
  }
350
358
  interface AiwgStaticSemanticQueryOptions {
351
359
  limit?: number;
@@ -411,6 +419,7 @@ declare function validateAiwgFortemiChunkManifest(value: unknown): AiwgChunkedIn
411
419
  declare function assertAiwgFortemiChunkManifest(value: unknown): AiwgFortemiChunkManifest;
412
420
  declare function validateAiwgFortemiChunkPart(value: unknown, partRef?: AiwgFortemiChunkPartRef, manifest?: AiwgFortemiChunkManifest): AiwgChunkedIndexValidationResult;
413
421
  declare function assertAiwgFortemiChunkPart(value: unknown, partRef?: AiwgFortemiChunkPartRef, manifest?: AiwgFortemiChunkManifest): AiwgFortemiChunkPart;
422
+ declare function resolveAiwgFetchUrl(href: string, baseUrl?: string | URL): string;
414
423
  declare function createAiwgFetchChunkLoader(baseUrl?: string | URL): AiwgChunkedIndexLoader;
415
424
  declare function encodeAiwgDetailId(id: string, encoding?: AiwgDetailIdEncoding): string;
416
425
  declare function aiwgDetailHrefForId(detail: AiwgFortemiChunkDetailRef, id: string): string;
@@ -423,6 +432,8 @@ interface AiwgChunkedIndexBuildOptions {
423
432
  detailHref?: string;
424
433
  idEncoding?: AiwgDetailIdEncoding;
425
434
  generatedAt?: string;
435
+ /** Privacy filtering (SEC6). Default-safe: excludes `private`/`pii` records. */
436
+ privacy?: AiwgPrivacyFilterOptions;
426
437
  }
427
438
  interface AiwgChunkedIndexBuildResult {
428
439
  manifest: AiwgFortemiChunkManifest;
@@ -442,7 +453,10 @@ declare function validateAiwgStaticEmbeddingSet(value: unknown): AiwgChunkedInde
442
453
  declare function assertAiwgStaticEmbeddingSet(value: unknown): AiwgStaticEmbeddingSet;
443
454
  declare function queryAiwgSemanticIndex(index: AiwgFortemiIndexExport, embeddingSet: AiwgStaticEmbeddingSet, queryEmbedding: number[], options?: AiwgStaticSemanticQueryOptions): AiwgStaticSemanticResult[];
444
455
  declare function queryAiwgHybridIndex(index: AiwgFortemiIndexExport, embeddingSet: AiwgStaticEmbeddingSet, query: string, queryEmbedding: number[], options?: AiwgStaticHybridQueryOptions): AiwgStaticSemanticResult[];
445
- declare function findAiwgStaticDuplicatePairs(index: AiwgFortemiIndexExport, embeddingSet: AiwgStaticEmbeddingSet, threshold?: number): AiwgStaticDuplicatePair[];
456
+ declare const DEFAULT_AIWG_DUPLICATE_SCAN_MAX_EMBEDDINGS = 5000;
457
+ declare function findAiwgStaticDuplicatePairs(index: AiwgFortemiIndexExport, embeddingSet: AiwgStaticEmbeddingSet, threshold?: number, options?: {
458
+ maxEmbeddings?: number;
459
+ }): AiwgStaticDuplicatePair[];
446
460
  declare function createAiwgReviewDecisionExport(source: Pick<AiwgFortemiIndexExport, 'schema_version'>, decisions: AiwgReviewDecision[], generatedAt?: string): AiwgReviewDecisionExport;
447
461
  declare function createAiwgIndexController(initialIndex?: AiwgFortemiIndexExport): AiwgIndexController;
448
462
  declare function aiwgFortemiIndexToCommunityGraph(index: AiwgFortemiIndexExport, options?: AiwgIndexGraphOptions): {
@@ -461,4 +475,4 @@ declare function aiwgFortemiIndexToCommunityGraph(index: AiwgFortemiIndexExport,
461
475
  }[];
462
476
  };
463
477
 
464
- export { AIWG_SCAN_REQUIRED_FIELDS, type AiwgChunkedIndexBuildOptions, type AiwgChunkedIndexBuildResult, type AiwgChunkedIndexDetailLoader, type AiwgChunkedIndexLoadOptions, type AiwgChunkedIndexLoader, type AiwgChunkedIndexProgress, type AiwgChunkedIndexProgressPhase, type AiwgChunkedIndexQueryOptions, type AiwgChunkedIndexQueryResult, type AiwgChunkedIndexValidationResult, type AiwgDetailIdEncoding, type AiwgFortemiAttachmentReference, type AiwgFortemiBinarySource, type AiwgFortemiChunk, type AiwgFortemiChunkDetailRef, type AiwgFortemiChunkManifest, type AiwgFortemiChunkPart, type AiwgFortemiChunkPartRef, type AiwgFortemiIndexExport, type AiwgFortemiIndexExportSchemaVersion, type AiwgFortemiKnownRecordType, type AiwgFortemiProjectedRecord, type AiwgFortemiProvenance, type AiwgFortemiProvenanceEvent, type AiwgFortemiRecord, type AiwgFortemiRecordEmbedding, type AiwgFortemiRecordSchemaVersion, type AiwgFortemiRecordSource, type AiwgFortemiRecordType, type AiwgFortemiRelationship, type AiwgFortemiRelationshipDirection, type AiwgFortemiSearchProjection, type AiwgFortemiSkosConcept, type AiwgFortemiSkosRelation, type AiwgFortemiSkosRelationType, type AiwgHeadlessEmbeddingBackend, type AiwgIndexController, type AiwgIndexControllerListener, type AiwgIndexControllerSnapshot, type AiwgIndexGraphOptions, type AiwgIndexQueryMatch, type AiwgIndexQueryOptions, type AiwgIndexQueryRankedItem, type AiwgIndexQueryResult, type AiwgIndexQueryWeights, type AiwgIndexValidationResult, type AiwgPrivacyClassification, type AiwgProvenanceConfidence, type AiwgRelationshipDirection, type AiwgRelationshipEdgeSummary, type AiwgRelationshipNodeSummary, type AiwgRelationshipQueryOptions, type AiwgRelationshipSetOperation, type AiwgRelationshipSetOptions, type AiwgRelationshipSetResult, type AiwgRelationshipTraversalOptions, type AiwgRelationshipTraversalResult, type AiwgReviewAction, type AiwgReviewDecision, type AiwgReviewDecisionExport, type AiwgReviewInput, type AiwgStaticDuplicatePair, type AiwgStaticEmbeddingRecord, type AiwgStaticEmbeddingSet, type AiwgStaticHybridQueryOptions, type AiwgStaticSemanticQueryOptions, type AiwgStaticSemanticResult, type BuildAiwgStaticEmbeddingSetOptions, aiwgDetailHrefForId, aiwgFortemiIndexToCommunityGraph, assertAiwgFortemiChunkManifest, assertAiwgFortemiChunkPart, assertAiwgFortemiIndexExport, assertAiwgStaticEmbeddingSet, buildAiwgChunkedIndex, buildAiwgStaticEmbeddingSet, createAiwgFetchChunkLoader, createAiwgFetchDetailLoader, createAiwgIndexController, createAiwgReviewDecisionExport, encodeAiwgDetailId, findAiwgStaticDuplicatePairs, getAiwgFortemiFacets, queryAiwgFortemiIndex, queryAiwgHybridIndex, queryAiwgSemanticIndex, validateAiwgFortemiChunkManifest, validateAiwgFortemiChunkPart, validateAiwgFortemiIndexExport, validateAiwgStaticEmbeddingSet };
478
+ export { AIWG_SCAN_REQUIRED_FIELDS, type AiwgChunkedIndexBuildOptions, type AiwgChunkedIndexBuildResult, type AiwgChunkedIndexDetailLoader, type AiwgChunkedIndexLoadOptions, type AiwgChunkedIndexLoader, type AiwgChunkedIndexProgress, type AiwgChunkedIndexProgressPhase, type AiwgChunkedIndexQueryOptions, type AiwgChunkedIndexQueryResult, type AiwgChunkedIndexValidationResult, type AiwgDetailIdEncoding, type AiwgFortemiAttachmentReference, type AiwgFortemiBinarySource, type AiwgFortemiChunk, type AiwgFortemiChunkDetailRef, type AiwgFortemiChunkManifest, type AiwgFortemiChunkPart, type AiwgFortemiChunkPartRef, type AiwgFortemiIndexExport, type AiwgFortemiIndexExportSchemaVersion, type AiwgFortemiKnownRecordType, type AiwgFortemiProjectedRecord, type AiwgFortemiProvenance, type AiwgFortemiProvenanceEvent, type AiwgFortemiRecord, type AiwgFortemiRecordEmbedding, type AiwgFortemiRecordSchemaVersion, type AiwgFortemiRecordSource, type AiwgFortemiRecordType, type AiwgFortemiRelationship, type AiwgFortemiRelationshipDirection, type AiwgFortemiSearchProjection, type AiwgFortemiSkosConcept, type AiwgFortemiSkosRelation, type AiwgFortemiSkosRelationType, type AiwgHeadlessEmbeddingBackend, type AiwgIndexController, type AiwgIndexControllerListener, type AiwgIndexControllerSnapshot, type AiwgIndexGraphOptions, type AiwgIndexQueryMatch, type AiwgIndexQueryOptions, type AiwgIndexQueryRankedItem, type AiwgIndexQueryResult, type AiwgIndexQueryWeights, type AiwgIndexValidationResult, type AiwgPrivacyClassification, type AiwgPrivacyFilterOptions, type AiwgProvenanceConfidence, type AiwgRelationshipDirection, type AiwgRelationshipEdgeSummary, type AiwgRelationshipNodeSummary, type AiwgRelationshipQueryOptions, type AiwgRelationshipSetOperation, type AiwgRelationshipSetOptions, type AiwgRelationshipSetResult, type AiwgRelationshipTraversalOptions, type AiwgRelationshipTraversalResult, type AiwgReviewAction, type AiwgReviewDecision, type AiwgReviewDecisionExport, type AiwgReviewInput, type AiwgStaticDuplicatePair, type AiwgStaticEmbeddingRecord, type AiwgStaticEmbeddingSet, type AiwgStaticHybridQueryOptions, type AiwgStaticSemanticQueryOptions, type AiwgStaticSemanticResult, type BuildAiwgStaticEmbeddingSetOptions, DEFAULT_AIWG_DUPLICATE_SCAN_MAX_EMBEDDINGS, aiwgDetailHrefForId, aiwgFortemiIndexToCommunityGraph, assertAiwgFortemiChunkManifest, assertAiwgFortemiChunkPart, assertAiwgFortemiIndexExport, assertAiwgStaticEmbeddingSet, buildAiwgChunkedIndex, buildAiwgStaticEmbeddingSet, createAiwgFetchChunkLoader, createAiwgFetchDetailLoader, createAiwgIndexController, createAiwgReviewDecisionExport, encodeAiwgDetailId, filterAiwgRecordsByPrivacy, findAiwgStaticDuplicatePairs, getAiwgFortemiFacets, queryAiwgFortemiIndex, queryAiwgHybridIndex, queryAiwgSemanticIndex, resolveAiwgFetchUrl, validateAiwgFortemiChunkManifest, validateAiwgFortemiChunkPart, validateAiwgFortemiIndexExport, validateAiwgStaticEmbeddingSet };
@@ -19,6 +19,16 @@ var AIWG_SCAN_REQUIRED_FIELDS = [
19
19
  "concepts",
20
20
  "privacy"
21
21
  ];
22
+ function isPrivacyExcluded(record, options) {
23
+ const privacy = record.privacy;
24
+ if (!privacy) return false;
25
+ if (privacy.classification === "private" && !options?.includePrivate) return true;
26
+ if (privacy.pii && !options?.includePii) return true;
27
+ return false;
28
+ }
29
+ function filterAiwgRecordsByPrivacy(records, options) {
30
+ return records.filter((record) => !isPrivacyExcluded(record, options));
31
+ }
22
32
  var REQUIRED_RECORD_FIELDS = [
23
33
  "schema_version",
24
34
  "id",
@@ -45,8 +55,12 @@ function hasString(value) {
45
55
  return typeof value === "string" && value.length > 0;
46
56
  }
47
57
  function pushFacet(counts, name, value) {
48
- counts[name] ??= {};
49
- counts[name][value] = (counts[name][value] ?? 0) + 1;
58
+ let bucket = counts[name];
59
+ if (bucket === void 0) {
60
+ bucket = /* @__PURE__ */ Object.create(null);
61
+ counts[name] = bucket;
62
+ }
63
+ bucket[value] = (bucket[value] ?? 0) + 1;
50
64
  }
51
65
  function hasNonNegativeInteger(value) {
52
66
  return Number.isInteger(value) && typeof value === "number" && value >= 0;
@@ -168,6 +182,57 @@ function validateOptionalRichMetadata(item, index, errors) {
168
182
  errors.push("items[" + index + "].compatibility must be an object");
169
183
  }
170
184
  }
185
+ function isPrivacyClassification(value) {
186
+ return value === "private" || value === "sanitized" || value === "public";
187
+ }
188
+ function isProvenanceConfidence(value) {
189
+ return value === "source" || value === "candidate" || value === "reviewed" || value === "rejected";
190
+ }
191
+ function validateProvenanceItems(item, index, errors) {
192
+ if (!Array.isArray(item.provenance)) return;
193
+ for (const [provIndex, prov] of item.provenance.entries()) {
194
+ const at = "items[" + index + "].provenance[" + provIndex + "]";
195
+ if (!isPlainRecord(prov)) {
196
+ errors.push(at + " must be an object");
197
+ continue;
198
+ }
199
+ if (!hasString(prov.field)) errors.push(at + ".field is required");
200
+ if (!hasString(prov.source)) errors.push(at + ".source is required");
201
+ if (!hasString(prov.path)) errors.push(at + ".path is required");
202
+ if (!isProvenanceConfidence(prov.confidence)) errors.push(at + ".confidence must be one of source, candidate, reviewed, rejected");
203
+ if (!isPrivacyClassification(prov.privacy)) errors.push(at + ".privacy must be one of private, sanitized, public");
204
+ }
205
+ }
206
+ var V2_ONLY_RECORD_FIELDS = ["search", "chunks", "embeddings", "skos_concepts", "skos_relations", "compatibility"];
207
+ var V2_ONLY_SOURCE_FIELDS = ["origin", "generated", "checksum", "updated_at"];
208
+ var V2_ONLY_RELATIONSHIP_FIELDS = ["target_path", "direction", "metadata"];
209
+ function forbidV2FieldsOnV1Record(item, index, errors) {
210
+ if (item.schema_version !== "aiwg.fortemi.index.record.v1") return;
211
+ const at = "items[" + index + "]";
212
+ const bag = item;
213
+ const v2msg = " is a v2-only field and must be absent on a record.v1 record";
214
+ for (const field of V2_ONLY_RECORD_FIELDS) {
215
+ if (bag[field] !== void 0) errors.push(at + "." + field + v2msg);
216
+ }
217
+ if (isPlainRecord(item.source)) {
218
+ const src = item.source;
219
+ for (const field of V2_ONLY_SOURCE_FIELDS) {
220
+ if (src[field] !== void 0) errors.push(at + ".source." + field + v2msg);
221
+ }
222
+ }
223
+ if (isPlainRecord(item.privacy) && item.privacy.locality !== void 0) {
224
+ errors.push(at + ".privacy.locality" + v2msg);
225
+ }
226
+ if (Array.isArray(item.relationships)) {
227
+ for (const [relIndex, rel] of item.relationships.entries()) {
228
+ if (!isPlainRecord(rel)) continue;
229
+ const relBag = rel;
230
+ for (const field of V2_ONLY_RELATIONSHIP_FIELDS) {
231
+ if (relBag[field] !== void 0) errors.push(at + ".relationships[" + relIndex + "]." + field + v2msg);
232
+ }
233
+ }
234
+ }
235
+ }
171
236
  function validateAiwgFortemiIndexExport(value) {
172
237
  const errors = [];
173
238
  const counts = {};
@@ -178,10 +243,21 @@ function validateAiwgFortemiIndexExport(value) {
178
243
  if (!hasString(data?.generated_at)) errors.push("generated_at is required");
179
244
  if (!hasString(data?.source?.repo)) errors.push("source.repo is required");
180
245
  if (!hasString(data?.source?.privacy)) errors.push("source.privacy is required");
246
+ else if (!isPrivacyClassification(data?.source?.privacy)) {
247
+ errors.push("source.privacy must be one of private, sanitized, public");
248
+ }
181
249
  if (!Array.isArray(data?.items)) errors.push("items must be an array");
182
250
  if (data.compatibility !== void 0 && !isPlainRecord(data.compatibility)) {
183
251
  errors.push("compatibility must be an object");
184
252
  }
253
+ if (data?.schema_version === "aiwg.fortemi.index.export.v1") {
254
+ if (isPlainRecord(data.source) && data.source.graph !== void 0) {
255
+ errors.push("source.graph is a v2-only field and must be absent on an export.v1 export");
256
+ }
257
+ if (data.compatibility !== void 0) {
258
+ errors.push("compatibility is a v2-only field and must be absent on an export.v1 export");
259
+ }
260
+ }
185
261
  const ids = /* @__PURE__ */ new Set();
186
262
  let previousId = "";
187
263
  for (const [index, item] of (data.items ?? []).entries()) {
@@ -216,8 +292,12 @@ function validateAiwgFortemiIndexExport(value) {
216
292
  errors.push("items[" + index + "].provenance must be a non-empty array");
217
293
  }
218
294
  validateOptionalRichMetadata(item, index, errors);
295
+ validateProvenanceItems(item, index, errors);
296
+ forbidV2FieldsOnV1Record(item, index, errors);
219
297
  if (!item.privacy || typeof item.privacy.pii !== "boolean" || !hasString(item.privacy.classification)) {
220
298
  errors.push("items[" + index + "].privacy requires classification and pii");
299
+ } else if (!isPrivacyClassification(item.privacy.classification)) {
300
+ errors.push("items[" + index + "].privacy.classification must be one of private, sanitized, public");
221
301
  }
222
302
  }
223
303
  return { valid: errors.length === 0, errors, counts };
@@ -238,6 +318,9 @@ function validateAiwgFortemiChunkManifest(value) {
238
318
  if (!hasString(data?.generated_at)) errors.push("generated_at is required");
239
319
  if (!hasString(data?.source?.repo)) errors.push("source.repo is required");
240
320
  if (!hasString(data?.source?.privacy)) errors.push("source.privacy is required");
321
+ if (data?.source_export_schema_version !== void 0 && !isSupportedIndexSchemaVersion(data.source_export_schema_version)) {
322
+ errors.push("source_export_schema_version must be aiwg.fortemi.index.export.v1 or aiwg.fortemi.index.export.v2 when present");
323
+ }
241
324
  if (!hasNonNegativeInteger(data?.total)) errors.push("total must be a non-negative integer");
242
325
  if (!hasPositiveInteger(data?.part_size)) errors.push("part_size must be a positive integer");
243
326
  if (data.facets !== void 0 && !isFacetCounts(data.facets)) {
@@ -356,9 +439,35 @@ function assertAiwgFortemiChunkPart(value, partRef, manifest) {
356
439
  }
357
440
  return value;
358
441
  }
442
+ var ALLOWED_AIWG_FETCH_SCHEMES = /* @__PURE__ */ new Set(["http:", "https:", "blob:", "data:"]);
443
+ function tryParseUrl(value) {
444
+ try {
445
+ return new URL(value);
446
+ } catch {
447
+ return null;
448
+ }
449
+ }
450
+ function resolveAiwgFetchUrl(href, baseUrl) {
451
+ if (baseUrl === void 0) {
452
+ const absolute = tryParseUrl(href);
453
+ if (absolute && !ALLOWED_AIWG_FETCH_SCHEMES.has(absolute.protocol)) {
454
+ throw new Error("Refusing AIWG index fetch with disallowed scheme: " + absolute.protocol);
455
+ }
456
+ return href;
457
+ }
458
+ const base = new URL(baseUrl);
459
+ const resolved = new URL(href, base);
460
+ if (!ALLOWED_AIWG_FETCH_SCHEMES.has(resolved.protocol)) {
461
+ throw new Error("Refusing AIWG index fetch with disallowed scheme: " + resolved.protocol);
462
+ }
463
+ if (resolved.origin !== base.origin) {
464
+ throw new Error("Refusing cross-origin AIWG index fetch: " + resolved.origin + " != " + base.origin);
465
+ }
466
+ return resolved.toString();
467
+ }
359
468
  function createAiwgFetchChunkLoader(baseUrl) {
360
469
  return async (part) => {
361
- const href = baseUrl ? new URL(part.href, baseUrl).toString() : part.href;
470
+ const href = resolveAiwgFetchUrl(part.href, baseUrl);
362
471
  const response = await fetch(href);
363
472
  if (!response.ok) throw new Error("Failed to fetch AIWG index chunk " + href + ": " + response.status);
364
473
  return response.json();
@@ -378,14 +487,14 @@ function createAiwgFetchDetailLoader(baseUrl) {
378
487
  return async (id, manifest) => {
379
488
  if (!manifest.detail?.href) throw new Error("Manifest has no detail.href for record resolution");
380
489
  const relative = aiwgDetailHrefForId(manifest.detail, id);
381
- const href = baseUrl ? new URL(relative, baseUrl).toString() : relative;
490
+ const href = resolveAiwgFetchUrl(relative, baseUrl);
382
491
  const response = await fetch(href);
383
492
  if (!response.ok) throw new Error("Failed to fetch AIWG index detail " + href + ": " + response.status);
384
493
  return response.json();
385
494
  };
386
495
  }
387
496
  function getAiwgFortemiFacets(items) {
388
- const result = {};
497
+ const result = /* @__PURE__ */ Object.create(null);
389
498
  for (const item of items) {
390
499
  pushFacet(result, "type", item.type);
391
500
  pushFacet(result, "privacy", item.privacy.classification);
@@ -421,7 +530,7 @@ function generatedAtString(value) {
421
530
  async function buildAiwgStaticEmbeddingSet(index, options) {
422
531
  assertAiwgFortemiIndexExport(index);
423
532
  const granularity = options.granularity ?? "body";
424
- const records = options.records ?? index.items;
533
+ const records = filterAiwgRecordsByPrivacy(options.records ?? index.items, options.privacy);
425
534
  const embeddings = [];
426
535
  for (const record of records) {
427
536
  const input = options.textForRecord?.(record) ?? defaultEmbeddingInput(record, granularity);
@@ -482,7 +591,7 @@ function buildAiwgChunkedIndex(index, options = {}) {
482
591
  const projection = options.projection;
483
592
  const idEncoding = options.idEncoding ?? "base64url";
484
593
  const detailHref = options.detailHref ?? "detail/{id}.json";
485
- const items = index.items;
594
+ const items = filterAiwgRecordsByPrivacy(index.items, options.privacy);
486
595
  const pad = (value) => String(value).padStart(4, "0");
487
596
  const project = (record) => {
488
597
  if (!projection) return record;
@@ -510,6 +619,7 @@ function buildAiwgChunkedIndex(index, options = {}) {
510
619
  schema_version: "aiwg.fortemi.index.chunk-manifest.v1",
511
620
  generated_at: options.generatedAt ?? index.generated_at,
512
621
  source: index.source,
622
+ source_export_schema_version: index.schema_version,
513
623
  total: items.length,
514
624
  part_size: partSize,
515
625
  facets: getAiwgFortemiFacets(items),
@@ -820,8 +930,15 @@ function queryAiwgHybridIndex(index, embeddingSet, query, queryEmbedding, option
820
930
  };
821
931
  }).filter((result) => result !== null && result.score >= (options.minScore ?? -1)).sort((left, right) => right.score - left.score || left.item.id.localeCompare(right.item.id)).slice(offset, offset + limit);
822
932
  }
823
- function findAiwgStaticDuplicatePairs(index, embeddingSet, threshold = 0.9) {
933
+ var DEFAULT_AIWG_DUPLICATE_SCAN_MAX_EMBEDDINGS = 5e3;
934
+ function findAiwgStaticDuplicatePairs(index, embeddingSet, threshold = 0.9, options) {
824
935
  assertAiwgStaticEmbeddingSet(embeddingSet);
936
+ const maxEmbeddings = options?.maxEmbeddings ?? DEFAULT_AIWG_DUPLICATE_SCAN_MAX_EMBEDDINGS;
937
+ if (embeddingSet.embeddings.length > maxEmbeddings) {
938
+ throw new Error(
939
+ "Embedding set too large for duplicate scan: " + embeddingSet.embeddings.length + " > " + maxEmbeddings + " (raise options.maxEmbeddings to override for trusted input)"
940
+ );
941
+ }
825
942
  const byId = new Map(index.items.map((item) => [item.id, item]));
826
943
  const pairs = [];
827
944
  for (let leftIndex = 0; leftIndex < embeddingSet.embeddings.length; leftIndex += 1) {
@@ -1286,7 +1403,7 @@ function createAiwgIndexController(initialIndex) {
1286
1403
  notify();
1287
1404
  },
1288
1405
  createReviewDecisionExport(generatedAt) {
1289
- const source = index ?? (chunked ? { schema_version: "aiwg.fortemi.index.export.v1" } : null);
1406
+ const source = index ?? (chunked ? { schema_version: chunked.manifest.source_export_schema_version ?? "aiwg.fortemi.index.export.v1" } : null);
1290
1407
  if (!source) throw new Error("No AIWG index export or chunked manifest loaded");
1291
1408
  return createAiwgReviewDecisionExport(source, reviewDecisions, generatedAt);
1292
1409
  },
@@ -1342,6 +1459,6 @@ function communityIdsFor(item, options) {
1342
1459
  return [`type:${item.type}`];
1343
1460
  }
1344
1461
 
1345
- export { AIWG_SCAN_REQUIRED_FIELDS, aiwgDetailHrefForId, aiwgFortemiIndexToCommunityGraph, assertAiwgFortemiChunkManifest, assertAiwgFortemiChunkPart, assertAiwgFortemiIndexExport, assertAiwgStaticEmbeddingSet, buildAiwgChunkedIndex, buildAiwgStaticEmbeddingSet, createAiwgFetchChunkLoader, createAiwgFetchDetailLoader, createAiwgIndexController, createAiwgReviewDecisionExport, encodeAiwgDetailId, findAiwgStaticDuplicatePairs, getAiwgFortemiFacets, queryAiwgFortemiIndex, queryAiwgHybridIndex, queryAiwgSemanticIndex, validateAiwgFortemiChunkManifest, validateAiwgFortemiChunkPart, validateAiwgFortemiIndexExport, validateAiwgStaticEmbeddingSet };
1462
+ export { AIWG_SCAN_REQUIRED_FIELDS, DEFAULT_AIWG_DUPLICATE_SCAN_MAX_EMBEDDINGS, aiwgDetailHrefForId, aiwgFortemiIndexToCommunityGraph, assertAiwgFortemiChunkManifest, assertAiwgFortemiChunkPart, assertAiwgFortemiIndexExport, assertAiwgStaticEmbeddingSet, buildAiwgChunkedIndex, buildAiwgStaticEmbeddingSet, createAiwgFetchChunkLoader, createAiwgFetchDetailLoader, createAiwgIndexController, createAiwgReviewDecisionExport, encodeAiwgDetailId, filterAiwgRecordsByPrivacy, findAiwgStaticDuplicatePairs, getAiwgFortemiFacets, queryAiwgFortemiIndex, queryAiwgHybridIndex, queryAiwgSemanticIndex, resolveAiwgFetchUrl, validateAiwgFortemiChunkManifest, validateAiwgFortemiChunkPart, validateAiwgFortemiIndexExport, validateAiwgStaticEmbeddingSet };
1346
1463
  //# sourceMappingURL=aiwg-index.js.map
1347
1464
  //# sourceMappingURL=aiwg-index.js.map