@fortemi/core 2026.7.1 → 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.
- package/README.md +16 -2
- package/dist/aiwg-index.d.ts +48 -7
- package/dist/aiwg-index.js +184 -10
- package/dist/aiwg-index.js.map +1 -1
- package/dist/index.d.ts +69 -32
- package/dist/index.js +346 -41
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -227,17 +227,31 @@ scan projection.
|
|
|
227
227
|
Static semantic search uses an optional sidecar contract:
|
|
228
228
|
|
|
229
229
|
```ts
|
|
230
|
-
import { queryAiwgHybridIndex } from '@fortemi/core/aiwg-index'
|
|
230
|
+
import { buildAiwgStaticEmbeddingSet, queryAiwgHybridIndex } from '@fortemi/core/aiwg-index'
|
|
231
231
|
|
|
232
232
|
const embeddingSet = await fetch('/search/aiwg-index/embeddings.json').then((res) => res.json())
|
|
233
233
|
const queryEmbedding = await hostEmbed('workspace health check')
|
|
234
234
|
const semanticResults = queryAiwgHybridIndex(index, embeddingSet, 'health check', queryEmbedding)
|
|
235
|
+
|
|
236
|
+
// Node/CLI generation path. Supply any Node-safe model backend; @fortemi/core
|
|
237
|
+
// only writes the shared embedding-set graph format and does not require DOM or WebGL.
|
|
238
|
+
const cliEmbeddingSet = await buildAiwgStaticEmbeddingSet(index, {
|
|
239
|
+
id: 'aiwg-cli-embeddings',
|
|
240
|
+
backend: {
|
|
241
|
+
model: 'local-node-embedder',
|
|
242
|
+
dimensions: 384,
|
|
243
|
+
embed: async (input) => nodeEmbed(input),
|
|
244
|
+
},
|
|
245
|
+
})
|
|
235
246
|
```
|
|
236
247
|
|
|
237
248
|
`aiwg.fortemi.embedding.set.v1` records the model, dimensions, granularity
|
|
238
249
|
(`title-summary`, `body`, `chunked-body`, or a project value), source record IDs,
|
|
239
250
|
and per-vector input hashes. Hosts provide query embeddings; `@fortemi/core` does
|
|
240
|
-
not add a model runtime dependency for static AIWG search.
|
|
251
|
+
not add a model runtime dependency for static AIWG search. The builder uses the
|
|
252
|
+
same record text projection as static search, including attachment
|
|
253
|
+
`extracted_text`, and records only attachment metadata references, never raw blob
|
|
254
|
+
bytes.
|
|
241
255
|
|
|
242
256
|
## What You Get
|
|
243
257
|
|
package/dist/aiwg-index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type AiwgFortemiKnownRecordType = 'crm.contact' | 'crm.organization' | 'crm.event' | 'crm.interaction' | 'aiwg.artifact' | '
|
|
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';
|
|
@@ -94,6 +94,17 @@ interface AiwgFortemiRecordEmbedding {
|
|
|
94
94
|
source_path?: string;
|
|
95
95
|
metadata?: Record<string, unknown>;
|
|
96
96
|
}
|
|
97
|
+
interface AiwgFortemiAttachmentReference {
|
|
98
|
+
id: string;
|
|
99
|
+
path: string;
|
|
100
|
+
mime: string | null;
|
|
101
|
+
checksum: string;
|
|
102
|
+
bytes: number;
|
|
103
|
+
}
|
|
104
|
+
interface AiwgFortemiBinarySource {
|
|
105
|
+
extracted_text: string;
|
|
106
|
+
attachment: AiwgFortemiAttachmentReference;
|
|
107
|
+
}
|
|
97
108
|
interface AiwgFortemiRecord {
|
|
98
109
|
schema_version: AiwgFortemiRecordSchemaVersion;
|
|
99
110
|
id: string;
|
|
@@ -108,6 +119,7 @@ interface AiwgFortemiRecord {
|
|
|
108
119
|
provenance: AiwgFortemiProvenance[];
|
|
109
120
|
search?: AiwgFortemiSearchProjection;
|
|
110
121
|
chunks?: AiwgFortemiChunk[];
|
|
122
|
+
binary_sources?: AiwgFortemiBinarySource[];
|
|
111
123
|
embeddings?: AiwgFortemiRecordEmbedding[];
|
|
112
124
|
compatibility?: Record<string, unknown>;
|
|
113
125
|
/** Optional rich SKOS metadata for static consumers that need labels/definitions without opening a shard. */
|
|
@@ -129,10 +141,7 @@ interface AiwgFortemiIndexExport {
|
|
|
129
141
|
source: {
|
|
130
142
|
repo: string;
|
|
131
143
|
privacy: AiwgPrivacyClassification;
|
|
132
|
-
|
|
133
|
-
generated?: boolean;
|
|
134
|
-
checksum?: string;
|
|
135
|
-
updated_at?: string;
|
|
144
|
+
graph?: Record<string, unknown>;
|
|
136
145
|
};
|
|
137
146
|
items: AiwgFortemiRecord[];
|
|
138
147
|
compatibility?: Record<string, unknown>;
|
|
@@ -153,6 +162,7 @@ interface AiwgFortemiChunkManifest {
|
|
|
153
162
|
schema_version: 'aiwg.fortemi.index.chunk-manifest.v1';
|
|
154
163
|
generated_at: string;
|
|
155
164
|
source: AiwgFortemiIndexExport['source'];
|
|
165
|
+
source_export_schema_version?: AiwgFortemiIndexExportSchemaVersion;
|
|
156
166
|
total: number;
|
|
157
167
|
part_size: number;
|
|
158
168
|
facets?: Record<string, Record<string, number>>;
|
|
@@ -321,6 +331,30 @@ interface AiwgStaticEmbeddingSet {
|
|
|
321
331
|
input_hash_algorithm?: string;
|
|
322
332
|
embeddings: AiwgStaticEmbeddingRecord[];
|
|
323
333
|
}
|
|
334
|
+
interface AiwgHeadlessEmbeddingBackend {
|
|
335
|
+
model: string;
|
|
336
|
+
dimensions: number;
|
|
337
|
+
embed(input: string, record: AiwgFortemiRecord): number[] | Promise<number[]>;
|
|
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[];
|
|
347
|
+
interface BuildAiwgStaticEmbeddingSetOptions {
|
|
348
|
+
id: string;
|
|
349
|
+
backend: AiwgHeadlessEmbeddingBackend;
|
|
350
|
+
records?: AiwgFortemiRecord[];
|
|
351
|
+
generatedAt?: string | Date;
|
|
352
|
+
granularity?: AiwgStaticEmbeddingSet['granularity'];
|
|
353
|
+
metric?: AiwgStaticEmbeddingSet['metric'];
|
|
354
|
+
textForRecord?: (record: AiwgFortemiRecord) => string;
|
|
355
|
+
/** Privacy filtering (SEC6). Default-safe: excludes `private`/`pii` records. */
|
|
356
|
+
privacy?: AiwgPrivacyFilterOptions;
|
|
357
|
+
}
|
|
324
358
|
interface AiwgStaticSemanticQueryOptions {
|
|
325
359
|
limit?: number;
|
|
326
360
|
offset?: number;
|
|
@@ -385,17 +419,21 @@ declare function validateAiwgFortemiChunkManifest(value: unknown): AiwgChunkedIn
|
|
|
385
419
|
declare function assertAiwgFortemiChunkManifest(value: unknown): AiwgFortemiChunkManifest;
|
|
386
420
|
declare function validateAiwgFortemiChunkPart(value: unknown, partRef?: AiwgFortemiChunkPartRef, manifest?: AiwgFortemiChunkManifest): AiwgChunkedIndexValidationResult;
|
|
387
421
|
declare function assertAiwgFortemiChunkPart(value: unknown, partRef?: AiwgFortemiChunkPartRef, manifest?: AiwgFortemiChunkManifest): AiwgFortemiChunkPart;
|
|
422
|
+
declare function resolveAiwgFetchUrl(href: string, baseUrl?: string | URL): string;
|
|
388
423
|
declare function createAiwgFetchChunkLoader(baseUrl?: string | URL): AiwgChunkedIndexLoader;
|
|
389
424
|
declare function encodeAiwgDetailId(id: string, encoding?: AiwgDetailIdEncoding): string;
|
|
390
425
|
declare function aiwgDetailHrefForId(detail: AiwgFortemiChunkDetailRef, id: string): string;
|
|
391
426
|
declare function createAiwgFetchDetailLoader(baseUrl?: string | URL): AiwgChunkedIndexDetailLoader;
|
|
392
427
|
declare function getAiwgFortemiFacets(items: AiwgFortemiRecord[]): Record<string, Record<string, number>>;
|
|
428
|
+
declare function buildAiwgStaticEmbeddingSet(index: AiwgFortemiIndexExport, options: BuildAiwgStaticEmbeddingSetOptions): Promise<AiwgStaticEmbeddingSet>;
|
|
393
429
|
interface AiwgChunkedIndexBuildOptions {
|
|
394
430
|
partSize?: number;
|
|
395
431
|
projection?: Array<keyof AiwgFortemiRecord>;
|
|
396
432
|
detailHref?: string;
|
|
397
433
|
idEncoding?: AiwgDetailIdEncoding;
|
|
398
434
|
generatedAt?: string;
|
|
435
|
+
/** Privacy filtering (SEC6). Default-safe: excludes `private`/`pii` records. */
|
|
436
|
+
privacy?: AiwgPrivacyFilterOptions;
|
|
399
437
|
}
|
|
400
438
|
interface AiwgChunkedIndexBuildResult {
|
|
401
439
|
manifest: AiwgFortemiChunkManifest;
|
|
@@ -415,7 +453,10 @@ declare function validateAiwgStaticEmbeddingSet(value: unknown): AiwgChunkedInde
|
|
|
415
453
|
declare function assertAiwgStaticEmbeddingSet(value: unknown): AiwgStaticEmbeddingSet;
|
|
416
454
|
declare function queryAiwgSemanticIndex(index: AiwgFortemiIndexExport, embeddingSet: AiwgStaticEmbeddingSet, queryEmbedding: number[], options?: AiwgStaticSemanticQueryOptions): AiwgStaticSemanticResult[];
|
|
417
455
|
declare function queryAiwgHybridIndex(index: AiwgFortemiIndexExport, embeddingSet: AiwgStaticEmbeddingSet, query: string, queryEmbedding: number[], options?: AiwgStaticHybridQueryOptions): AiwgStaticSemanticResult[];
|
|
418
|
-
declare
|
|
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[];
|
|
419
460
|
declare function createAiwgReviewDecisionExport(source: Pick<AiwgFortemiIndexExport, 'schema_version'>, decisions: AiwgReviewDecision[], generatedAt?: string): AiwgReviewDecisionExport;
|
|
420
461
|
declare function createAiwgIndexController(initialIndex?: AiwgFortemiIndexExport): AiwgIndexController;
|
|
421
462
|
declare function aiwgFortemiIndexToCommunityGraph(index: AiwgFortemiIndexExport, options?: AiwgIndexGraphOptions): {
|
|
@@ -434,4 +475,4 @@ declare function aiwgFortemiIndexToCommunityGraph(index: AiwgFortemiIndexExport,
|
|
|
434
475
|
}[];
|
|
435
476
|
};
|
|
436
477
|
|
|
437
|
-
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 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 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, aiwgDetailHrefForId, aiwgFortemiIndexToCommunityGraph, assertAiwgFortemiChunkManifest, assertAiwgFortemiChunkPart, assertAiwgFortemiIndexExport, assertAiwgStaticEmbeddingSet, buildAiwgChunkedIndex, 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 };
|
package/dist/aiwg-index.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
2
|
+
import { bytesToHex } from '@noble/hashes/utils';
|
|
3
|
+
|
|
4
|
+
// src/hash.ts
|
|
5
|
+
function computeHash(data) {
|
|
6
|
+
const digest = sha256(data);
|
|
7
|
+
return `sha256:${bytesToHex(digest)}`;
|
|
8
|
+
}
|
|
9
|
+
|
|
1
10
|
// src/aiwg-index.ts
|
|
2
11
|
var AIWG_SCAN_REQUIRED_FIELDS = [
|
|
3
12
|
"schema_version",
|
|
@@ -10,6 +19,16 @@ var AIWG_SCAN_REQUIRED_FIELDS = [
|
|
|
10
19
|
"concepts",
|
|
11
20
|
"privacy"
|
|
12
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
|
+
}
|
|
13
32
|
var REQUIRED_RECORD_FIELDS = [
|
|
14
33
|
"schema_version",
|
|
15
34
|
"id",
|
|
@@ -36,8 +55,12 @@ function hasString(value) {
|
|
|
36
55
|
return typeof value === "string" && value.length > 0;
|
|
37
56
|
}
|
|
38
57
|
function pushFacet(counts, name, value) {
|
|
39
|
-
counts[name]
|
|
40
|
-
|
|
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;
|
|
41
64
|
}
|
|
42
65
|
function hasNonNegativeInteger(value) {
|
|
43
66
|
return Number.isInteger(value) && typeof value === "number" && value >= 0;
|
|
@@ -159,6 +182,57 @@ function validateOptionalRichMetadata(item, index, errors) {
|
|
|
159
182
|
errors.push("items[" + index + "].compatibility must be an object");
|
|
160
183
|
}
|
|
161
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
|
+
}
|
|
162
236
|
function validateAiwgFortemiIndexExport(value) {
|
|
163
237
|
const errors = [];
|
|
164
238
|
const counts = {};
|
|
@@ -169,10 +243,21 @@ function validateAiwgFortemiIndexExport(value) {
|
|
|
169
243
|
if (!hasString(data?.generated_at)) errors.push("generated_at is required");
|
|
170
244
|
if (!hasString(data?.source?.repo)) errors.push("source.repo is required");
|
|
171
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
|
+
}
|
|
172
249
|
if (!Array.isArray(data?.items)) errors.push("items must be an array");
|
|
173
250
|
if (data.compatibility !== void 0 && !isPlainRecord(data.compatibility)) {
|
|
174
251
|
errors.push("compatibility must be an object");
|
|
175
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
|
+
}
|
|
176
261
|
const ids = /* @__PURE__ */ new Set();
|
|
177
262
|
let previousId = "";
|
|
178
263
|
for (const [index, item] of (data.items ?? []).entries()) {
|
|
@@ -207,8 +292,12 @@ function validateAiwgFortemiIndexExport(value) {
|
|
|
207
292
|
errors.push("items[" + index + "].provenance must be a non-empty array");
|
|
208
293
|
}
|
|
209
294
|
validateOptionalRichMetadata(item, index, errors);
|
|
295
|
+
validateProvenanceItems(item, index, errors);
|
|
296
|
+
forbidV2FieldsOnV1Record(item, index, errors);
|
|
210
297
|
if (!item.privacy || typeof item.privacy.pii !== "boolean" || !hasString(item.privacy.classification)) {
|
|
211
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");
|
|
212
301
|
}
|
|
213
302
|
}
|
|
214
303
|
return { valid: errors.length === 0, errors, counts };
|
|
@@ -229,6 +318,9 @@ function validateAiwgFortemiChunkManifest(value) {
|
|
|
229
318
|
if (!hasString(data?.generated_at)) errors.push("generated_at is required");
|
|
230
319
|
if (!hasString(data?.source?.repo)) errors.push("source.repo is required");
|
|
231
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
|
+
}
|
|
232
324
|
if (!hasNonNegativeInteger(data?.total)) errors.push("total must be a non-negative integer");
|
|
233
325
|
if (!hasPositiveInteger(data?.part_size)) errors.push("part_size must be a positive integer");
|
|
234
326
|
if (data.facets !== void 0 && !isFacetCounts(data.facets)) {
|
|
@@ -347,9 +439,35 @@ function assertAiwgFortemiChunkPart(value, partRef, manifest) {
|
|
|
347
439
|
}
|
|
348
440
|
return value;
|
|
349
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
|
+
}
|
|
350
468
|
function createAiwgFetchChunkLoader(baseUrl) {
|
|
351
469
|
return async (part) => {
|
|
352
|
-
const href =
|
|
470
|
+
const href = resolveAiwgFetchUrl(part.href, baseUrl);
|
|
353
471
|
const response = await fetch(href);
|
|
354
472
|
if (!response.ok) throw new Error("Failed to fetch AIWG index chunk " + href + ": " + response.status);
|
|
355
473
|
return response.json();
|
|
@@ -369,14 +487,14 @@ function createAiwgFetchDetailLoader(baseUrl) {
|
|
|
369
487
|
return async (id, manifest) => {
|
|
370
488
|
if (!manifest.detail?.href) throw new Error("Manifest has no detail.href for record resolution");
|
|
371
489
|
const relative = aiwgDetailHrefForId(manifest.detail, id);
|
|
372
|
-
const href =
|
|
490
|
+
const href = resolveAiwgFetchUrl(relative, baseUrl);
|
|
373
491
|
const response = await fetch(href);
|
|
374
492
|
if (!response.ok) throw new Error("Failed to fetch AIWG index detail " + href + ": " + response.status);
|
|
375
493
|
return response.json();
|
|
376
494
|
};
|
|
377
495
|
}
|
|
378
496
|
function getAiwgFortemiFacets(items) {
|
|
379
|
-
const result =
|
|
497
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
380
498
|
for (const item of items) {
|
|
381
499
|
pushFacet(result, "type", item.type);
|
|
382
500
|
pushFacet(result, "privacy", item.privacy.classification);
|
|
@@ -392,7 +510,55 @@ function recordTitle(item) {
|
|
|
392
510
|
return item.title ?? item.search?.title ?? item.search?.name ?? item.id;
|
|
393
511
|
}
|
|
394
512
|
function recordText(item) {
|
|
395
|
-
|
|
513
|
+
const base = item.text ?? item.search?.body ?? item.search?.summary ?? item.chunks?.map((chunk) => chunk.text ?? chunk.body ?? chunk.summary ?? "").filter(Boolean).join("\n") ?? "";
|
|
514
|
+
const extractedText = "binary_sources" in item ? item.binary_sources?.map((source) => source.extracted_text).filter(Boolean).join("\n") ?? "" : "";
|
|
515
|
+
return [base, extractedText].filter(Boolean).join("\n");
|
|
516
|
+
}
|
|
517
|
+
function defaultEmbeddingInput(record, granularity) {
|
|
518
|
+
const title = recordTitle(record);
|
|
519
|
+
const text = recordText(record);
|
|
520
|
+
if (granularity === "title-summary") {
|
|
521
|
+
const extractedText = record.binary_sources?.map((source) => source.extracted_text).filter(Boolean).join("\n") ?? "";
|
|
522
|
+
return [title, record.search?.summary ?? "", extractedText].filter(Boolean).join("\n");
|
|
523
|
+
}
|
|
524
|
+
return [title, text].filter(Boolean).join("\n");
|
|
525
|
+
}
|
|
526
|
+
function generatedAtString(value) {
|
|
527
|
+
if (value instanceof Date) return value.toISOString();
|
|
528
|
+
return value ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
529
|
+
}
|
|
530
|
+
async function buildAiwgStaticEmbeddingSet(index, options) {
|
|
531
|
+
assertAiwgFortemiIndexExport(index);
|
|
532
|
+
const granularity = options.granularity ?? "body";
|
|
533
|
+
const records = filterAiwgRecordsByPrivacy(options.records ?? index.items, options.privacy);
|
|
534
|
+
const embeddings = [];
|
|
535
|
+
for (const record of records) {
|
|
536
|
+
const input = options.textForRecord?.(record) ?? defaultEmbeddingInput(record, granularity);
|
|
537
|
+
const embedding = await options.backend.embed(input, record);
|
|
538
|
+
if (embedding.length !== options.backend.dimensions) {
|
|
539
|
+
throw new Error(`Embedding for ${record.id} has ${embedding.length} dimensions; expected ${options.backend.dimensions}`);
|
|
540
|
+
}
|
|
541
|
+
embeddings.push({
|
|
542
|
+
record_id: record.id,
|
|
543
|
+
embedding,
|
|
544
|
+
granularity,
|
|
545
|
+
input_hash: computeHash(new TextEncoder().encode(input)),
|
|
546
|
+
source_path: record.source.path
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
const embeddingSet = {
|
|
550
|
+
schema_version: "aiwg.fortemi.embedding.set.v1",
|
|
551
|
+
id: options.id,
|
|
552
|
+
model: options.backend.model,
|
|
553
|
+
dimensions: options.backend.dimensions,
|
|
554
|
+
generated_at: generatedAtString(options.generatedAt),
|
|
555
|
+
granularity,
|
|
556
|
+
...options.metric ? { metric: options.metric } : {},
|
|
557
|
+
input_hash_algorithm: "sha256",
|
|
558
|
+
embeddings
|
|
559
|
+
};
|
|
560
|
+
assertAiwgStaticEmbeddingSet(embeddingSet);
|
|
561
|
+
return embeddingSet;
|
|
396
562
|
}
|
|
397
563
|
function recordSearchValues(item) {
|
|
398
564
|
const search = item.search;
|
|
@@ -425,7 +591,7 @@ function buildAiwgChunkedIndex(index, options = {}) {
|
|
|
425
591
|
const projection = options.projection;
|
|
426
592
|
const idEncoding = options.idEncoding ?? "base64url";
|
|
427
593
|
const detailHref = options.detailHref ?? "detail/{id}.json";
|
|
428
|
-
const items = index.items;
|
|
594
|
+
const items = filterAiwgRecordsByPrivacy(index.items, options.privacy);
|
|
429
595
|
const pad = (value) => String(value).padStart(4, "0");
|
|
430
596
|
const project = (record) => {
|
|
431
597
|
if (!projection) return record;
|
|
@@ -453,6 +619,7 @@ function buildAiwgChunkedIndex(index, options = {}) {
|
|
|
453
619
|
schema_version: "aiwg.fortemi.index.chunk-manifest.v1",
|
|
454
620
|
generated_at: options.generatedAt ?? index.generated_at,
|
|
455
621
|
source: index.source,
|
|
622
|
+
source_export_schema_version: index.schema_version,
|
|
456
623
|
total: items.length,
|
|
457
624
|
part_size: partSize,
|
|
458
625
|
facets: getAiwgFortemiFacets(items),
|
|
@@ -763,8 +930,15 @@ function queryAiwgHybridIndex(index, embeddingSet, query, queryEmbedding, option
|
|
|
763
930
|
};
|
|
764
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);
|
|
765
932
|
}
|
|
766
|
-
|
|
933
|
+
var DEFAULT_AIWG_DUPLICATE_SCAN_MAX_EMBEDDINGS = 5e3;
|
|
934
|
+
function findAiwgStaticDuplicatePairs(index, embeddingSet, threshold = 0.9, options) {
|
|
767
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
|
+
}
|
|
768
942
|
const byId = new Map(index.items.map((item) => [item.id, item]));
|
|
769
943
|
const pairs = [];
|
|
770
944
|
for (let leftIndex = 0; leftIndex < embeddingSet.embeddings.length; leftIndex += 1) {
|
|
@@ -1229,7 +1403,7 @@ function createAiwgIndexController(initialIndex) {
|
|
|
1229
1403
|
notify();
|
|
1230
1404
|
},
|
|
1231
1405
|
createReviewDecisionExport(generatedAt) {
|
|
1232
|
-
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);
|
|
1233
1407
|
if (!source) throw new Error("No AIWG index export or chunked manifest loaded");
|
|
1234
1408
|
return createAiwgReviewDecisionExport(source, reviewDecisions, generatedAt);
|
|
1235
1409
|
},
|
|
@@ -1285,6 +1459,6 @@ function communityIdsFor(item, options) {
|
|
|
1285
1459
|
return [`type:${item.type}`];
|
|
1286
1460
|
}
|
|
1287
1461
|
|
|
1288
|
-
export { AIWG_SCAN_REQUIRED_FIELDS, aiwgDetailHrefForId, aiwgFortemiIndexToCommunityGraph, assertAiwgFortemiChunkManifest, assertAiwgFortemiChunkPart, assertAiwgFortemiIndexExport, assertAiwgStaticEmbeddingSet, buildAiwgChunkedIndex, 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 };
|
|
1289
1463
|
//# sourceMappingURL=aiwg-index.js.map
|
|
1290
1464
|
//# sourceMappingURL=aiwg-index.js.map
|