@fortemi/core 2026.7.2 → 2026.7.4
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 +3 -3
- package/dist/aiwg-index-C2G7iy-b.d.ts +826 -0
- package/dist/aiwg-index.d.ts +1 -464
- package/dist/aiwg-index.js +1118 -89
- package/dist/aiwg-index.js.map +1 -1
- package/dist/index.d.ts +213 -300
- package/dist/index.js +2914 -222
- package/dist/index.js.map +1 -1
- package/package.json +15 -3
- package/tools/verify-fortemi-compatibility.mjs +82 -0
package/dist/aiwg-index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { sha256 } from '@noble/hashes/sha256';
|
|
2
|
+
import '@noble/hashes/blake3';
|
|
2
3
|
import { bytesToHex } from '@noble/hashes/utils';
|
|
4
|
+
import Ajv2020 from 'ajv/dist/2020.js';
|
|
3
5
|
|
|
4
6
|
// src/hash.ts
|
|
5
7
|
function computeHash(data) {
|
|
@@ -7,6 +9,720 @@ function computeHash(data) {
|
|
|
7
9
|
return `sha256:${bytesToHex(digest)}`;
|
|
8
10
|
}
|
|
9
11
|
|
|
12
|
+
// schemas/aiwg-fortemi-index-export.schema.json
|
|
13
|
+
var aiwg_fortemi_index_export_schema_default = {
|
|
14
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
15
|
+
$id: "https://aiwg.io/schemas/aiwg-fortemi-index-export.json",
|
|
16
|
+
title: "AIWG Fortemi Index Export",
|
|
17
|
+
type: "object",
|
|
18
|
+
additionalProperties: false,
|
|
19
|
+
required: ["schema_version", "generated_at", "source", "items"],
|
|
20
|
+
properties: {
|
|
21
|
+
schema_version: {
|
|
22
|
+
enum: ["aiwg.fortemi.index.export.v1", "aiwg.fortemi.index.export.v2"]
|
|
23
|
+
},
|
|
24
|
+
generated_at: {
|
|
25
|
+
type: "string",
|
|
26
|
+
format: "date-time"
|
|
27
|
+
},
|
|
28
|
+
source: {
|
|
29
|
+
type: "object",
|
|
30
|
+
additionalProperties: false,
|
|
31
|
+
required: ["repo", "privacy"],
|
|
32
|
+
properties: {
|
|
33
|
+
repo: {
|
|
34
|
+
type: "string",
|
|
35
|
+
minLength: 1
|
|
36
|
+
},
|
|
37
|
+
privacy: {
|
|
38
|
+
$ref: "#/$defs/privacy"
|
|
39
|
+
},
|
|
40
|
+
graph: {
|
|
41
|
+
type: "string",
|
|
42
|
+
minLength: 1
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
compatibility: {
|
|
47
|
+
type: "object",
|
|
48
|
+
additionalProperties: false,
|
|
49
|
+
required: ["previous_schema_version", "strategy"],
|
|
50
|
+
properties: {
|
|
51
|
+
previous_schema_version: {
|
|
52
|
+
const: "aiwg.fortemi.index.export.v1"
|
|
53
|
+
},
|
|
54
|
+
strategy: {
|
|
55
|
+
const: "supported"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
items: {
|
|
60
|
+
type: "array",
|
|
61
|
+
items: {
|
|
62
|
+
$ref: "#/$defs/record"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
allOf: [
|
|
67
|
+
{
|
|
68
|
+
if: {
|
|
69
|
+
properties: {
|
|
70
|
+
schema_version: {
|
|
71
|
+
const: "aiwg.fortemi.index.export.v1"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
then: {
|
|
76
|
+
not: {
|
|
77
|
+
required: ["compatibility"]
|
|
78
|
+
},
|
|
79
|
+
properties: {
|
|
80
|
+
source: {
|
|
81
|
+
not: {
|
|
82
|
+
required: ["graph"]
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
items: {
|
|
86
|
+
items: {
|
|
87
|
+
allOf: [
|
|
88
|
+
{
|
|
89
|
+
properties: {
|
|
90
|
+
schema_version: {
|
|
91
|
+
const: "aiwg.fortemi.index.record.v1"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
$ref: "#/$defs/v1RecordCompatibility"
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
if: {
|
|
106
|
+
properties: {
|
|
107
|
+
schema_version: {
|
|
108
|
+
const: "aiwg.fortemi.index.export.v2"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
then: {
|
|
113
|
+
required: ["compatibility"],
|
|
114
|
+
properties: {
|
|
115
|
+
source: {
|
|
116
|
+
required: ["graph"]
|
|
117
|
+
},
|
|
118
|
+
items: {
|
|
119
|
+
items: {
|
|
120
|
+
properties: {
|
|
121
|
+
schema_version: {
|
|
122
|
+
const: "aiwg.fortemi.index.record.v2"
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
],
|
|
131
|
+
$defs: {
|
|
132
|
+
privacy: {
|
|
133
|
+
enum: ["private", "sanitized", "public"]
|
|
134
|
+
},
|
|
135
|
+
recordType: {
|
|
136
|
+
type: "string",
|
|
137
|
+
minLength: 1
|
|
138
|
+
},
|
|
139
|
+
stringArray: {
|
|
140
|
+
type: "array",
|
|
141
|
+
items: {
|
|
142
|
+
type: "string"
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
numberArray: {
|
|
146
|
+
type: "array",
|
|
147
|
+
items: {
|
|
148
|
+
type: "number"
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
v1RecordCompatibility: {
|
|
152
|
+
type: "object",
|
|
153
|
+
not: {
|
|
154
|
+
anyOf: [
|
|
155
|
+
{
|
|
156
|
+
required: ["name"]
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
required: ["summary"]
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
required: ["search"]
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
required: ["chunks"]
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
required: ["embeddings"]
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
required: ["compatibility"]
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
required: ["skos_concepts"]
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
required: ["skos_relations"]
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
required: ["provenance_events"]
|
|
181
|
+
}
|
|
182
|
+
]
|
|
183
|
+
},
|
|
184
|
+
properties: {
|
|
185
|
+
source: {
|
|
186
|
+
not: {
|
|
187
|
+
anyOf: [
|
|
188
|
+
{
|
|
189
|
+
required: ["origin"]
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
required: ["generated"]
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
required: ["checksum"]
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
required: ["updated_at"]
|
|
199
|
+
}
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
relationships: {
|
|
204
|
+
items: {
|
|
205
|
+
not: {
|
|
206
|
+
anyOf: [
|
|
207
|
+
{
|
|
208
|
+
required: ["target_path"]
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
required: ["direction"]
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
required: ["label"]
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
required: ["confidence"]
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
required: ["privacy"]
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
required: ["metadata"]
|
|
224
|
+
}
|
|
225
|
+
]
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
privacy: {
|
|
230
|
+
not: {
|
|
231
|
+
required: ["locality"]
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
record: {
|
|
237
|
+
type: "object",
|
|
238
|
+
additionalProperties: false,
|
|
239
|
+
required: [
|
|
240
|
+
"schema_version",
|
|
241
|
+
"id",
|
|
242
|
+
"type",
|
|
243
|
+
"source",
|
|
244
|
+
"title",
|
|
245
|
+
"text",
|
|
246
|
+
"facets",
|
|
247
|
+
"tags",
|
|
248
|
+
"concepts",
|
|
249
|
+
"relationships",
|
|
250
|
+
"provenance",
|
|
251
|
+
"privacy",
|
|
252
|
+
"updated_at"
|
|
253
|
+
],
|
|
254
|
+
properties: {
|
|
255
|
+
schema_version: {
|
|
256
|
+
enum: [
|
|
257
|
+
"aiwg.fortemi.index.record.v1",
|
|
258
|
+
"aiwg.fortemi.index.record.v2"
|
|
259
|
+
]
|
|
260
|
+
},
|
|
261
|
+
id: {
|
|
262
|
+
type: "string",
|
|
263
|
+
minLength: 1
|
|
264
|
+
},
|
|
265
|
+
type: {
|
|
266
|
+
$ref: "#/$defs/recordType"
|
|
267
|
+
},
|
|
268
|
+
source: {
|
|
269
|
+
type: "object",
|
|
270
|
+
additionalProperties: false,
|
|
271
|
+
required: ["path", "repo_relative_path", "locator"],
|
|
272
|
+
properties: {
|
|
273
|
+
path: {
|
|
274
|
+
type: "string",
|
|
275
|
+
minLength: 1
|
|
276
|
+
},
|
|
277
|
+
repo_relative_path: {
|
|
278
|
+
type: "string",
|
|
279
|
+
minLength: 1
|
|
280
|
+
},
|
|
281
|
+
locator: {
|
|
282
|
+
type: "string",
|
|
283
|
+
minLength: 1
|
|
284
|
+
},
|
|
285
|
+
origin: {
|
|
286
|
+
type: "string",
|
|
287
|
+
minLength: 1
|
|
288
|
+
},
|
|
289
|
+
generated: {
|
|
290
|
+
type: "boolean"
|
|
291
|
+
},
|
|
292
|
+
checksum: {
|
|
293
|
+
type: "string"
|
|
294
|
+
},
|
|
295
|
+
updated_at: {
|
|
296
|
+
type: "string",
|
|
297
|
+
format: "date-time"
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
title: {
|
|
302
|
+
type: "string"
|
|
303
|
+
},
|
|
304
|
+
name: {
|
|
305
|
+
type: "string"
|
|
306
|
+
},
|
|
307
|
+
summary: {
|
|
308
|
+
type: "string"
|
|
309
|
+
},
|
|
310
|
+
text: {
|
|
311
|
+
type: "string"
|
|
312
|
+
},
|
|
313
|
+
search: {
|
|
314
|
+
type: "object",
|
|
315
|
+
additionalProperties: false,
|
|
316
|
+
required: [
|
|
317
|
+
"title",
|
|
318
|
+
"body",
|
|
319
|
+
"triggers",
|
|
320
|
+
"aliases",
|
|
321
|
+
"tags",
|
|
322
|
+
"frontmatter"
|
|
323
|
+
],
|
|
324
|
+
properties: {
|
|
325
|
+
title: {
|
|
326
|
+
type: "string"
|
|
327
|
+
},
|
|
328
|
+
name: {
|
|
329
|
+
type: "string"
|
|
330
|
+
},
|
|
331
|
+
summary: {
|
|
332
|
+
type: "string"
|
|
333
|
+
},
|
|
334
|
+
body: {
|
|
335
|
+
type: "string"
|
|
336
|
+
},
|
|
337
|
+
triggers: {
|
|
338
|
+
$ref: "#/$defs/stringArray"
|
|
339
|
+
},
|
|
340
|
+
aliases: {
|
|
341
|
+
$ref: "#/$defs/stringArray"
|
|
342
|
+
},
|
|
343
|
+
capability: {
|
|
344
|
+
type: "string"
|
|
345
|
+
},
|
|
346
|
+
tags: {
|
|
347
|
+
$ref: "#/$defs/stringArray"
|
|
348
|
+
},
|
|
349
|
+
phase: {
|
|
350
|
+
type: "string"
|
|
351
|
+
},
|
|
352
|
+
type: {
|
|
353
|
+
type: "string"
|
|
354
|
+
},
|
|
355
|
+
frontmatter: {
|
|
356
|
+
type: "object"
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
facets: {
|
|
361
|
+
type: "object",
|
|
362
|
+
additionalProperties: {
|
|
363
|
+
$ref: "#/$defs/stringArray"
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
tags: {
|
|
367
|
+
$ref: "#/$defs/stringArray"
|
|
368
|
+
},
|
|
369
|
+
concepts: {
|
|
370
|
+
$ref: "#/$defs/stringArray"
|
|
371
|
+
},
|
|
372
|
+
relationships: {
|
|
373
|
+
type: "array",
|
|
374
|
+
items: {
|
|
375
|
+
type: "object",
|
|
376
|
+
additionalProperties: false,
|
|
377
|
+
required: ["type", "target_id"],
|
|
378
|
+
properties: {
|
|
379
|
+
type: {
|
|
380
|
+
type: "string",
|
|
381
|
+
minLength: 1
|
|
382
|
+
},
|
|
383
|
+
target_id: {
|
|
384
|
+
type: "string",
|
|
385
|
+
minLength: 1
|
|
386
|
+
},
|
|
387
|
+
source_path: {
|
|
388
|
+
type: "string"
|
|
389
|
+
},
|
|
390
|
+
target_path: {
|
|
391
|
+
type: "string"
|
|
392
|
+
},
|
|
393
|
+
direction: {
|
|
394
|
+
enum: ["upstream", "downstream", "related"]
|
|
395
|
+
},
|
|
396
|
+
label: {
|
|
397
|
+
type: "string"
|
|
398
|
+
},
|
|
399
|
+
confidence: {
|
|
400
|
+
type: "number"
|
|
401
|
+
},
|
|
402
|
+
privacy: {
|
|
403
|
+
$ref: "#/$defs/privacy"
|
|
404
|
+
},
|
|
405
|
+
metadata: {
|
|
406
|
+
type: "object"
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
provenance: {
|
|
412
|
+
type: "array",
|
|
413
|
+
minItems: 1,
|
|
414
|
+
items: {
|
|
415
|
+
type: "object",
|
|
416
|
+
additionalProperties: false,
|
|
417
|
+
required: ["field", "source", "path", "confidence", "privacy"],
|
|
418
|
+
properties: {
|
|
419
|
+
field: {
|
|
420
|
+
type: "string",
|
|
421
|
+
minLength: 1
|
|
422
|
+
},
|
|
423
|
+
source: {
|
|
424
|
+
type: "string",
|
|
425
|
+
minLength: 1
|
|
426
|
+
},
|
|
427
|
+
path: {
|
|
428
|
+
type: "string",
|
|
429
|
+
minLength: 1
|
|
430
|
+
},
|
|
431
|
+
confidence: {
|
|
432
|
+
enum: ["source", "candidate", "reviewed", "rejected"]
|
|
433
|
+
},
|
|
434
|
+
privacy: {
|
|
435
|
+
$ref: "#/$defs/privacy"
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
privacy: {
|
|
441
|
+
type: "object",
|
|
442
|
+
additionalProperties: false,
|
|
443
|
+
required: ["classification", "pii"],
|
|
444
|
+
properties: {
|
|
445
|
+
classification: {
|
|
446
|
+
$ref: "#/$defs/privacy"
|
|
447
|
+
},
|
|
448
|
+
pii: {
|
|
449
|
+
type: "boolean"
|
|
450
|
+
},
|
|
451
|
+
locality: {
|
|
452
|
+
enum: ["project", "framework", "external"]
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
chunks: {
|
|
457
|
+
type: "array",
|
|
458
|
+
items: {
|
|
459
|
+
type: "object",
|
|
460
|
+
additionalProperties: false,
|
|
461
|
+
properties: {
|
|
462
|
+
id: {
|
|
463
|
+
type: "string",
|
|
464
|
+
minLength: 1
|
|
465
|
+
},
|
|
466
|
+
text: {
|
|
467
|
+
type: "string"
|
|
468
|
+
},
|
|
469
|
+
body: {
|
|
470
|
+
type: "string"
|
|
471
|
+
},
|
|
472
|
+
summary: {
|
|
473
|
+
type: "string"
|
|
474
|
+
},
|
|
475
|
+
source_path: {
|
|
476
|
+
type: "string"
|
|
477
|
+
},
|
|
478
|
+
metadata: {
|
|
479
|
+
type: "object"
|
|
480
|
+
},
|
|
481
|
+
checksum: {
|
|
482
|
+
type: "string"
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
},
|
|
487
|
+
embeddings: {
|
|
488
|
+
type: "array",
|
|
489
|
+
items: {
|
|
490
|
+
type: "object",
|
|
491
|
+
additionalProperties: false,
|
|
492
|
+
properties: {
|
|
493
|
+
id: {
|
|
494
|
+
type: "string"
|
|
495
|
+
},
|
|
496
|
+
model: {
|
|
497
|
+
type: "string"
|
|
498
|
+
},
|
|
499
|
+
embedding: {
|
|
500
|
+
$ref: "#/$defs/numberArray"
|
|
501
|
+
},
|
|
502
|
+
vector: {
|
|
503
|
+
$ref: "#/$defs/numberArray"
|
|
504
|
+
},
|
|
505
|
+
granularity: {
|
|
506
|
+
type: "string"
|
|
507
|
+
},
|
|
508
|
+
source_path: {
|
|
509
|
+
type: "string"
|
|
510
|
+
},
|
|
511
|
+
metadata: {
|
|
512
|
+
type: "object"
|
|
513
|
+
},
|
|
514
|
+
chunk_id: {
|
|
515
|
+
type: "string"
|
|
516
|
+
},
|
|
517
|
+
vector_ref: {
|
|
518
|
+
type: "string"
|
|
519
|
+
},
|
|
520
|
+
input_hash: {
|
|
521
|
+
type: "string"
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
},
|
|
526
|
+
compatibility: {
|
|
527
|
+
type: "object"
|
|
528
|
+
},
|
|
529
|
+
skos_concepts: {
|
|
530
|
+
type: "array",
|
|
531
|
+
items: {
|
|
532
|
+
type: "object",
|
|
533
|
+
additionalProperties: false,
|
|
534
|
+
required: ["id", "prefLabel"],
|
|
535
|
+
properties: {
|
|
536
|
+
id: {
|
|
537
|
+
type: "string",
|
|
538
|
+
minLength: 1
|
|
539
|
+
},
|
|
540
|
+
prefLabel: {
|
|
541
|
+
type: "string",
|
|
542
|
+
minLength: 1
|
|
543
|
+
},
|
|
544
|
+
definition: {
|
|
545
|
+
type: "string"
|
|
546
|
+
},
|
|
547
|
+
scheme: {
|
|
548
|
+
type: "string"
|
|
549
|
+
},
|
|
550
|
+
notation: {
|
|
551
|
+
type: "string"
|
|
552
|
+
},
|
|
553
|
+
uri: {
|
|
554
|
+
type: "string"
|
|
555
|
+
},
|
|
556
|
+
altLabels: {
|
|
557
|
+
$ref: "#/$defs/stringArray"
|
|
558
|
+
},
|
|
559
|
+
metadata: {
|
|
560
|
+
type: "object"
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
skos_relations: {
|
|
566
|
+
type: "array",
|
|
567
|
+
items: {
|
|
568
|
+
type: "object",
|
|
569
|
+
additionalProperties: false,
|
|
570
|
+
required: ["type", "source_id", "target_id"],
|
|
571
|
+
properties: {
|
|
572
|
+
type: {
|
|
573
|
+
type: "string",
|
|
574
|
+
minLength: 1
|
|
575
|
+
},
|
|
576
|
+
source_id: {
|
|
577
|
+
type: "string",
|
|
578
|
+
minLength: 1
|
|
579
|
+
},
|
|
580
|
+
target_id: {
|
|
581
|
+
type: "string",
|
|
582
|
+
minLength: 1
|
|
583
|
+
},
|
|
584
|
+
source_path: {
|
|
585
|
+
type: "string"
|
|
586
|
+
},
|
|
587
|
+
metadata: {
|
|
588
|
+
type: "object"
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
},
|
|
593
|
+
provenance_events: {
|
|
594
|
+
type: "array",
|
|
595
|
+
items: {
|
|
596
|
+
type: "object",
|
|
597
|
+
additionalProperties: false,
|
|
598
|
+
required: ["activity"],
|
|
599
|
+
properties: {
|
|
600
|
+
id: {
|
|
601
|
+
type: "string"
|
|
602
|
+
},
|
|
603
|
+
activity: {
|
|
604
|
+
type: "string",
|
|
605
|
+
minLength: 1
|
|
606
|
+
},
|
|
607
|
+
agent: {
|
|
608
|
+
type: "string"
|
|
609
|
+
},
|
|
610
|
+
started_at: {
|
|
611
|
+
type: "string",
|
|
612
|
+
format: "date-time"
|
|
613
|
+
},
|
|
614
|
+
ended_at: {
|
|
615
|
+
type: "string",
|
|
616
|
+
format: "date-time"
|
|
617
|
+
},
|
|
618
|
+
source: {
|
|
619
|
+
type: "string"
|
|
620
|
+
},
|
|
621
|
+
path: {
|
|
622
|
+
type: "string"
|
|
623
|
+
},
|
|
624
|
+
confidence: {
|
|
625
|
+
enum: ["source", "candidate", "reviewed", "rejected"]
|
|
626
|
+
},
|
|
627
|
+
privacy: {
|
|
628
|
+
$ref: "#/$defs/privacy"
|
|
629
|
+
},
|
|
630
|
+
attributes: {
|
|
631
|
+
type: "object"
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
},
|
|
636
|
+
updated_at: {
|
|
637
|
+
type: "string",
|
|
638
|
+
format: "date-time"
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
// src/aiwg-index-schema.ts
|
|
646
|
+
var PROJECTED_FIELDS = [
|
|
647
|
+
"schema_version",
|
|
648
|
+
"id",
|
|
649
|
+
"type",
|
|
650
|
+
"title",
|
|
651
|
+
"text",
|
|
652
|
+
"facets",
|
|
653
|
+
"tags",
|
|
654
|
+
"concepts",
|
|
655
|
+
"privacy"
|
|
656
|
+
];
|
|
657
|
+
var ajvInstance;
|
|
658
|
+
var exportValidator;
|
|
659
|
+
var projectedRecordValidator;
|
|
660
|
+
function getAjv() {
|
|
661
|
+
if (!ajvInstance) {
|
|
662
|
+
ajvInstance = new Ajv2020({
|
|
663
|
+
allErrors: true,
|
|
664
|
+
strict: false,
|
|
665
|
+
validateFormats: false
|
|
666
|
+
});
|
|
667
|
+
ajvInstance.addSchema(aiwg_fortemi_index_export_schema_default);
|
|
668
|
+
}
|
|
669
|
+
return ajvInstance;
|
|
670
|
+
}
|
|
671
|
+
function formatErrors(errors) {
|
|
672
|
+
return (errors ?? []).map((error) => {
|
|
673
|
+
const path = error.instancePath || "(root)";
|
|
674
|
+
return `${path} ${error.message ?? "is invalid"}`;
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
function getExportValidator() {
|
|
678
|
+
exportValidator ??= getAjv().getSchema(aiwg_fortemi_index_export_schema_default.$id) ?? getAjv().compile(aiwg_fortemi_index_export_schema_default);
|
|
679
|
+
return exportValidator;
|
|
680
|
+
}
|
|
681
|
+
function getProjectedRecordValidator() {
|
|
682
|
+
if (!projectedRecordValidator) {
|
|
683
|
+
const properties = Object.fromEntries(Object.keys(aiwg_fortemi_index_export_schema_default.$defs.record.properties).map((field) => [
|
|
684
|
+
field,
|
|
685
|
+
{ $ref: `${aiwg_fortemi_index_export_schema_default.$id}#/$defs/record/properties/${field}` }
|
|
686
|
+
]));
|
|
687
|
+
projectedRecordValidator = getAjv().compile({
|
|
688
|
+
type: "object",
|
|
689
|
+
required: PROJECTED_FIELDS,
|
|
690
|
+
properties,
|
|
691
|
+
additionalProperties: true,
|
|
692
|
+
allOf: [
|
|
693
|
+
{
|
|
694
|
+
if: {
|
|
695
|
+
properties: {
|
|
696
|
+
schema_version: { const: "aiwg.fortemi.index.record.v1" }
|
|
697
|
+
}
|
|
698
|
+
},
|
|
699
|
+
then: { $ref: `${aiwg_fortemi_index_export_schema_default.$id}#/$defs/v1RecordCompatibility` }
|
|
700
|
+
}
|
|
701
|
+
]
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
return projectedRecordValidator;
|
|
705
|
+
}
|
|
706
|
+
function validateAiwgFortemiIndexExportSchema(value) {
|
|
707
|
+
const validate = getExportValidator();
|
|
708
|
+
const schemaValue = value && typeof value === "object" && Array.isArray(value.items) ? {
|
|
709
|
+
...value,
|
|
710
|
+
items: value.items.map((item) => {
|
|
711
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return item;
|
|
712
|
+
const schemaRecord = { ...item };
|
|
713
|
+
Reflect.deleteProperty(schemaRecord, "binary_sources");
|
|
714
|
+
return schemaRecord;
|
|
715
|
+
})
|
|
716
|
+
} : value;
|
|
717
|
+
const valid = validate(schemaValue);
|
|
718
|
+
return { valid, errors: formatErrors(validate.errors) };
|
|
719
|
+
}
|
|
720
|
+
function validateAiwgFortemiProjectedRecordSchema(value) {
|
|
721
|
+
const validate = getProjectedRecordValidator();
|
|
722
|
+
const valid = validate(value);
|
|
723
|
+
return { valid, errors: formatErrors(validate.errors) };
|
|
724
|
+
}
|
|
725
|
+
|
|
10
726
|
// src/aiwg-index.ts
|
|
11
727
|
var AIWG_SCAN_REQUIRED_FIELDS = [
|
|
12
728
|
"schema_version",
|
|
@@ -19,6 +735,16 @@ var AIWG_SCAN_REQUIRED_FIELDS = [
|
|
|
19
735
|
"concepts",
|
|
20
736
|
"privacy"
|
|
21
737
|
];
|
|
738
|
+
function isPrivacyExcluded(record, options) {
|
|
739
|
+
const privacy = record.privacy;
|
|
740
|
+
if (!privacy || !isPrivacyClassification(privacy.classification) || typeof privacy.pii !== "boolean") return true;
|
|
741
|
+
if (privacy.classification === "private" && !options?.includePrivate) return true;
|
|
742
|
+
if (privacy.pii && !options?.includePii) return true;
|
|
743
|
+
return false;
|
|
744
|
+
}
|
|
745
|
+
function filterAiwgRecordsByPrivacy(records, options) {
|
|
746
|
+
return records.filter((record) => !isPrivacyExcluded(record, options));
|
|
747
|
+
}
|
|
22
748
|
var REQUIRED_RECORD_FIELDS = [
|
|
23
749
|
"schema_version",
|
|
24
750
|
"id",
|
|
@@ -45,8 +771,12 @@ function hasString(value) {
|
|
|
45
771
|
return typeof value === "string" && value.length > 0;
|
|
46
772
|
}
|
|
47
773
|
function pushFacet(counts, name, value) {
|
|
48
|
-
counts[name]
|
|
49
|
-
|
|
774
|
+
let bucket = counts[name];
|
|
775
|
+
if (bucket === void 0) {
|
|
776
|
+
bucket = /* @__PURE__ */ Object.create(null);
|
|
777
|
+
counts[name] = bucket;
|
|
778
|
+
}
|
|
779
|
+
bucket[value] = (bucket[value] ?? 0) + 1;
|
|
50
780
|
}
|
|
51
781
|
function hasNonNegativeInteger(value) {
|
|
52
782
|
return Number.isInteger(value) && typeof value === "number" && value >= 0;
|
|
@@ -76,6 +806,10 @@ function validateOptionalRichMetadata(item, index, errors) {
|
|
|
76
806
|
errors.push("items[" + index + "].skos_concepts must be an array when present");
|
|
77
807
|
} else {
|
|
78
808
|
for (const [conceptIndex, concept] of item.skos_concepts.entries()) {
|
|
809
|
+
if (!isPlainRecord(concept)) {
|
|
810
|
+
errors.push("items[" + index + "].skos_concepts[" + conceptIndex + "] must be an object");
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
79
813
|
if (!hasString(concept.id)) errors.push("items[" + index + "].skos_concepts[" + conceptIndex + "].id is required");
|
|
80
814
|
if (!hasString(concept.prefLabel)) errors.push("items[" + index + "].skos_concepts[" + conceptIndex + "].prefLabel is required");
|
|
81
815
|
if (!isOptionalStringArray(concept.altLabels)) errors.push("items[" + index + "].skos_concepts[" + conceptIndex + "].altLabels must be a string array");
|
|
@@ -90,6 +824,10 @@ function validateOptionalRichMetadata(item, index, errors) {
|
|
|
90
824
|
errors.push("items[" + index + "].skos_relations must be an array when present");
|
|
91
825
|
} else {
|
|
92
826
|
for (const [relationIndex, relation] of item.skos_relations.entries()) {
|
|
827
|
+
if (!isPlainRecord(relation)) {
|
|
828
|
+
errors.push("items[" + index + "].skos_relations[" + relationIndex + "] must be an object");
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
93
831
|
if (!hasString(relation.type)) errors.push("items[" + index + "].skos_relations[" + relationIndex + "].type is required");
|
|
94
832
|
if (!hasString(relation.source_id)) errors.push("items[" + index + "].skos_relations[" + relationIndex + "].source_id is required");
|
|
95
833
|
if (!hasString(relation.target_id)) errors.push("items[" + index + "].skos_relations[" + relationIndex + "].target_id is required");
|
|
@@ -104,6 +842,10 @@ function validateOptionalRichMetadata(item, index, errors) {
|
|
|
104
842
|
errors.push("items[" + index + "].provenance_events must be an array when present");
|
|
105
843
|
} else {
|
|
106
844
|
for (const [eventIndex, event] of item.provenance_events.entries()) {
|
|
845
|
+
if (!isPlainRecord(event)) {
|
|
846
|
+
errors.push("items[" + index + "].provenance_events[" + eventIndex + "] must be an object");
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
107
849
|
if (!hasString(event.activity)) errors.push("items[" + index + "].provenance_events[" + eventIndex + "].activity is required");
|
|
108
850
|
if (event.attributes !== void 0 && !isPlainRecord(event.attributes)) {
|
|
109
851
|
errors.push("items[" + index + "].provenance_events[" + eventIndex + "].attributes must be an object");
|
|
@@ -113,6 +855,10 @@ function validateOptionalRichMetadata(item, index, errors) {
|
|
|
113
855
|
}
|
|
114
856
|
if (Array.isArray(item.relationships)) {
|
|
115
857
|
for (const [relationshipIndex, relationship] of item.relationships.entries()) {
|
|
858
|
+
if (!isPlainRecord(relationship)) {
|
|
859
|
+
errors.push("items[" + index + "].relationships[" + relationshipIndex + "] must be an object");
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
116
862
|
if (relationship.metadata !== void 0 && !isPlainRecord(relationship.metadata)) {
|
|
117
863
|
errors.push("items[" + index + "].relationships[" + relationshipIndex + "].metadata must be an object");
|
|
118
864
|
}
|
|
@@ -141,7 +887,10 @@ function validateOptionalRichMetadata(item, index, errors) {
|
|
|
141
887
|
errors.push("items[" + index + "].chunks must be an array when present");
|
|
142
888
|
} else {
|
|
143
889
|
for (const [chunkIndex, chunk] of item.chunks.entries()) {
|
|
144
|
-
if (!isPlainRecord(chunk))
|
|
890
|
+
if (!isPlainRecord(chunk)) {
|
|
891
|
+
errors.push("items[" + index + "].chunks[" + chunkIndex + "] must be an object");
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
145
894
|
if (chunk.metadata !== void 0 && !isPlainRecord(chunk.metadata)) {
|
|
146
895
|
errors.push("items[" + index + "].chunks[" + chunkIndex + "].metadata must be an object");
|
|
147
896
|
}
|
|
@@ -153,7 +902,10 @@ function validateOptionalRichMetadata(item, index, errors) {
|
|
|
153
902
|
errors.push("items[" + index + "].embeddings must be an array when present");
|
|
154
903
|
} else {
|
|
155
904
|
for (const [embeddingIndex, embedding] of item.embeddings.entries()) {
|
|
156
|
-
if (!isPlainRecord(embedding))
|
|
905
|
+
if (!isPlainRecord(embedding)) {
|
|
906
|
+
errors.push("items[" + index + "].embeddings[" + embeddingIndex + "] must be an object");
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
157
909
|
const vector = embedding.embedding ?? embedding.vector;
|
|
158
910
|
if (vector !== void 0 && (!Array.isArray(vector) || !vector.every((entry) => typeof entry === "number"))) {
|
|
159
911
|
errors.push("items[" + index + "].embeddings[" + embeddingIndex + "].embedding/vector must be a number array");
|
|
@@ -168,23 +920,91 @@ function validateOptionalRichMetadata(item, index, errors) {
|
|
|
168
920
|
errors.push("items[" + index + "].compatibility must be an object");
|
|
169
921
|
}
|
|
170
922
|
}
|
|
923
|
+
function isPrivacyClassification(value) {
|
|
924
|
+
return value === "private" || value === "sanitized" || value === "public";
|
|
925
|
+
}
|
|
926
|
+
function isProvenanceConfidence(value) {
|
|
927
|
+
return value === "source" || value === "candidate" || value === "reviewed" || value === "rejected";
|
|
928
|
+
}
|
|
929
|
+
function validateProvenanceItems(item, index, errors) {
|
|
930
|
+
if (!Array.isArray(item.provenance)) return;
|
|
931
|
+
for (const [provIndex, prov] of item.provenance.entries()) {
|
|
932
|
+
const at = "items[" + index + "].provenance[" + provIndex + "]";
|
|
933
|
+
if (!isPlainRecord(prov)) {
|
|
934
|
+
errors.push(at + " must be an object");
|
|
935
|
+
continue;
|
|
936
|
+
}
|
|
937
|
+
if (!hasString(prov.field)) errors.push(at + ".field is required");
|
|
938
|
+
if (!hasString(prov.source)) errors.push(at + ".source is required");
|
|
939
|
+
if (!hasString(prov.path)) errors.push(at + ".path is required");
|
|
940
|
+
if (!isProvenanceConfidence(prov.confidence)) errors.push(at + ".confidence must be one of source, candidate, reviewed, rejected");
|
|
941
|
+
if (!isPrivacyClassification(prov.privacy)) errors.push(at + ".privacy must be one of private, sanitized, public");
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
var V2_ONLY_RECORD_FIELDS = ["search", "chunks", "embeddings", "skos_concepts", "skos_relations", "compatibility"];
|
|
945
|
+
var V2_ONLY_SOURCE_FIELDS = ["origin", "generated", "checksum", "updated_at"];
|
|
946
|
+
var V2_ONLY_RELATIONSHIP_FIELDS = ["target_path", "direction", "metadata"];
|
|
947
|
+
function forbidV2FieldsOnV1Record(item, index, errors) {
|
|
948
|
+
if (item.schema_version !== "aiwg.fortemi.index.record.v1") return;
|
|
949
|
+
const at = "items[" + index + "]";
|
|
950
|
+
const bag = item;
|
|
951
|
+
const v2msg = " is a v2-only field and must be absent on a record.v1 record";
|
|
952
|
+
for (const field of V2_ONLY_RECORD_FIELDS) {
|
|
953
|
+
if (bag[field] !== void 0) errors.push(at + "." + field + v2msg);
|
|
954
|
+
}
|
|
955
|
+
if (isPlainRecord(item.source)) {
|
|
956
|
+
const src = item.source;
|
|
957
|
+
for (const field of V2_ONLY_SOURCE_FIELDS) {
|
|
958
|
+
if (src[field] !== void 0) errors.push(at + ".source." + field + v2msg);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
if (isPlainRecord(item.privacy) && item.privacy.locality !== void 0) {
|
|
962
|
+
errors.push(at + ".privacy.locality" + v2msg);
|
|
963
|
+
}
|
|
964
|
+
if (Array.isArray(item.relationships)) {
|
|
965
|
+
for (const [relIndex, rel] of item.relationships.entries()) {
|
|
966
|
+
if (!isPlainRecord(rel)) continue;
|
|
967
|
+
const relBag = rel;
|
|
968
|
+
for (const field of V2_ONLY_RELATIONSHIP_FIELDS) {
|
|
969
|
+
if (relBag[field] !== void 0) errors.push(at + ".relationships[" + relIndex + "]." + field + v2msg);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
171
974
|
function validateAiwgFortemiIndexExport(value) {
|
|
172
|
-
const errors =
|
|
173
|
-
const counts =
|
|
174
|
-
const data = value;
|
|
975
|
+
const errors = validateAiwgFortemiIndexExportSchema(value).errors;
|
|
976
|
+
const counts = /* @__PURE__ */ Object.create(null);
|
|
977
|
+
const data = isPlainRecord(value) ? value : {};
|
|
978
|
+
if (!isPlainRecord(value)) errors.push("index export must be an object");
|
|
175
979
|
if (!isSupportedIndexSchemaVersion(data?.schema_version)) {
|
|
176
980
|
errors.push("schema_version must be aiwg.fortemi.index.export.v1 or aiwg.fortemi.index.export.v2");
|
|
177
981
|
}
|
|
178
982
|
if (!hasString(data?.generated_at)) errors.push("generated_at is required");
|
|
179
983
|
if (!hasString(data?.source?.repo)) errors.push("source.repo is required");
|
|
180
984
|
if (!hasString(data?.source?.privacy)) errors.push("source.privacy is required");
|
|
985
|
+
else if (!isPrivacyClassification(data?.source?.privacy)) {
|
|
986
|
+
errors.push("source.privacy must be one of private, sanitized, public");
|
|
987
|
+
}
|
|
181
988
|
if (!Array.isArray(data?.items)) errors.push("items must be an array");
|
|
182
989
|
if (data.compatibility !== void 0 && !isPlainRecord(data.compatibility)) {
|
|
183
990
|
errors.push("compatibility must be an object");
|
|
184
991
|
}
|
|
992
|
+
if (data?.schema_version === "aiwg.fortemi.index.export.v1") {
|
|
993
|
+
if (isPlainRecord(data.source) && data.source.graph !== void 0) {
|
|
994
|
+
errors.push("source.graph is a v2-only field and must be absent on an export.v1 export");
|
|
995
|
+
}
|
|
996
|
+
if (data.compatibility !== void 0) {
|
|
997
|
+
errors.push("compatibility is a v2-only field and must be absent on an export.v1 export");
|
|
998
|
+
}
|
|
999
|
+
}
|
|
185
1000
|
const ids = /* @__PURE__ */ new Set();
|
|
186
1001
|
let previousId = "";
|
|
187
|
-
|
|
1002
|
+
const items = Array.isArray(data.items) ? data.items : [];
|
|
1003
|
+
for (const [index, item] of items.entries()) {
|
|
1004
|
+
if (!isPlainRecord(item)) {
|
|
1005
|
+
errors.push("items[" + index + "] must be an object");
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
188
1008
|
for (const field of REQUIRED_RECORD_FIELDS) {
|
|
189
1009
|
if (!(field in item)) errors.push("items[" + index + "]." + field + " is required");
|
|
190
1010
|
}
|
|
@@ -194,7 +1014,7 @@ function validateAiwgFortemiIndexExport(value) {
|
|
|
194
1014
|
if (!hasString(item.id)) errors.push("items[" + index + "].id is required");
|
|
195
1015
|
if (hasString(item.id) && ids.has(item.id)) errors.push("duplicate id: " + item.id);
|
|
196
1016
|
if (hasString(item.id)) ids.add(item.id);
|
|
197
|
-
if (previousId && hasString(item.id) && previousId
|
|
1017
|
+
if (previousId && hasString(item.id) && previousId > item.id) {
|
|
198
1018
|
errors.push("items must be sorted by id: " + previousId + " before " + item.id);
|
|
199
1019
|
}
|
|
200
1020
|
if (hasString(item.id)) previousId = item.id;
|
|
@@ -216,8 +1036,12 @@ function validateAiwgFortemiIndexExport(value) {
|
|
|
216
1036
|
errors.push("items[" + index + "].provenance must be a non-empty array");
|
|
217
1037
|
}
|
|
218
1038
|
validateOptionalRichMetadata(item, index, errors);
|
|
1039
|
+
validateProvenanceItems(item, index, errors);
|
|
1040
|
+
forbidV2FieldsOnV1Record(item, index, errors);
|
|
219
1041
|
if (!item.privacy || typeof item.privacy.pii !== "boolean" || !hasString(item.privacy.classification)) {
|
|
220
1042
|
errors.push("items[" + index + "].privacy requires classification and pii");
|
|
1043
|
+
} else if (!isPrivacyClassification(item.privacy.classification)) {
|
|
1044
|
+
errors.push("items[" + index + "].privacy.classification must be one of private, sanitized, public");
|
|
221
1045
|
}
|
|
222
1046
|
}
|
|
223
1047
|
return { valid: errors.length === 0, errors, counts };
|
|
@@ -231,13 +1055,17 @@ function assertAiwgFortemiIndexExport(value) {
|
|
|
231
1055
|
}
|
|
232
1056
|
function validateAiwgFortemiChunkManifest(value) {
|
|
233
1057
|
const errors = [];
|
|
234
|
-
const data = value;
|
|
1058
|
+
const data = isPlainRecord(value) ? value : {};
|
|
1059
|
+
if (!isPlainRecord(value)) errors.push("chunk manifest must be an object");
|
|
235
1060
|
if (data?.schema_version !== "aiwg.fortemi.index.chunk-manifest.v1") {
|
|
236
1061
|
errors.push("schema_version must be aiwg.fortemi.index.chunk-manifest.v1");
|
|
237
1062
|
}
|
|
238
1063
|
if (!hasString(data?.generated_at)) errors.push("generated_at is required");
|
|
239
1064
|
if (!hasString(data?.source?.repo)) errors.push("source.repo is required");
|
|
240
1065
|
if (!hasString(data?.source?.privacy)) errors.push("source.privacy is required");
|
|
1066
|
+
if (data?.source_export_schema_version !== void 0 && !isSupportedIndexSchemaVersion(data.source_export_schema_version)) {
|
|
1067
|
+
errors.push("source_export_schema_version must be aiwg.fortemi.index.export.v1 or aiwg.fortemi.index.export.v2 when present");
|
|
1068
|
+
}
|
|
241
1069
|
if (!hasNonNegativeInteger(data?.total)) errors.push("total must be a non-negative integer");
|
|
242
1070
|
if (!hasPositiveInteger(data?.part_size)) errors.push("part_size must be a positive integer");
|
|
243
1071
|
if (data.facets !== void 0 && !isFacetCounts(data.facets)) {
|
|
@@ -253,7 +1081,9 @@ function validateAiwgFortemiChunkManifest(value) {
|
|
|
253
1081
|
}
|
|
254
1082
|
}
|
|
255
1083
|
}
|
|
256
|
-
if (data.detail !== void 0) {
|
|
1084
|
+
if (data.detail !== void 0 && !isPlainRecord(data.detail)) {
|
|
1085
|
+
errors.push("detail must be an object");
|
|
1086
|
+
} else if (data.detail !== void 0) {
|
|
257
1087
|
if (!hasString(data.detail.href)) errors.push("detail.href is required");
|
|
258
1088
|
else if (!data.detail.href.includes("{id}")) errors.push("detail.href must contain the {id} placeholder");
|
|
259
1089
|
if (data.detail.encoding !== void 0 && data.detail.encoding !== "uri" && data.detail.encoding !== "base64url") {
|
|
@@ -264,6 +1094,10 @@ function validateAiwgFortemiChunkManifest(value) {
|
|
|
264
1094
|
let expectedOffset = 0;
|
|
265
1095
|
const parts = Array.isArray(data?.parts) ? data.parts : [];
|
|
266
1096
|
for (const [index, part] of parts.entries()) {
|
|
1097
|
+
if (!isPlainRecord(part)) {
|
|
1098
|
+
errors.push("parts[" + index + "] must be an object");
|
|
1099
|
+
continue;
|
|
1100
|
+
}
|
|
267
1101
|
if (!hasString(part.href)) errors.push("parts[" + index + "].href is required");
|
|
268
1102
|
if (!hasNonNegativeInteger(part.offset)) errors.push("parts[" + index + "].offset must be a non-negative integer");
|
|
269
1103
|
if (!hasNonNegativeInteger(part.count)) errors.push("parts[" + index + "].count must be a non-negative integer");
|
|
@@ -284,18 +1118,28 @@ function assertAiwgFortemiChunkManifest(value) {
|
|
|
284
1118
|
}
|
|
285
1119
|
return value;
|
|
286
1120
|
}
|
|
287
|
-
function validateProjectedRecords(items) {
|
|
1121
|
+
function validateProjectedRecords(items, sourceSchemaVersion) {
|
|
288
1122
|
const errors = [];
|
|
289
1123
|
const ids = /* @__PURE__ */ new Set();
|
|
290
1124
|
let previousId = "";
|
|
291
1125
|
for (const [index, item] of items.entries()) {
|
|
1126
|
+
if (!isPlainRecord(item)) {
|
|
1127
|
+
errors.push("items[" + index + "] must be an object");
|
|
1128
|
+
continue;
|
|
1129
|
+
}
|
|
1130
|
+
const schemaValidation = validateAiwgFortemiProjectedRecordSchema(item);
|
|
1131
|
+
errors.push(...schemaValidation.errors.map((error) => `items[${index}]${error}`));
|
|
1132
|
+
const expectedRecordVersion = sourceSchemaVersion === "aiwg.fortemi.index.export.v2" ? "aiwg.fortemi.index.record.v2" : "aiwg.fortemi.index.record.v1";
|
|
1133
|
+
if (item.schema_version !== expectedRecordVersion) {
|
|
1134
|
+
errors.push(`items[${index}].schema_version must match ${sourceSchemaVersion}`);
|
|
1135
|
+
}
|
|
292
1136
|
if (!isSupportedRecordSchemaVersion(item.schema_version)) {
|
|
293
1137
|
errors.push("items[" + index + "].schema_version must be aiwg.fortemi.index.record.v1 or aiwg.fortemi.index.record.v2");
|
|
294
1138
|
}
|
|
295
1139
|
if (!hasString(item.id)) errors.push("items[" + index + "].id is required");
|
|
296
1140
|
if (hasString(item.id) && ids.has(item.id)) errors.push("duplicate id: " + item.id);
|
|
297
1141
|
if (hasString(item.id)) ids.add(item.id);
|
|
298
|
-
if (previousId && hasString(item.id) && previousId
|
|
1142
|
+
if (previousId && hasString(item.id) && previousId > item.id) {
|
|
299
1143
|
errors.push("items must be sorted by id: " + previousId + " before " + item.id);
|
|
300
1144
|
}
|
|
301
1145
|
if (hasString(item.id)) previousId = item.id;
|
|
@@ -311,15 +1155,13 @@ function validateProjectedRecords(items) {
|
|
|
311
1155
|
}
|
|
312
1156
|
if (!Array.isArray(item.tags)) errors.push("items[" + index + "].tags must be an array");
|
|
313
1157
|
if (!Array.isArray(item.concepts)) errors.push("items[" + index + "].concepts must be an array");
|
|
314
|
-
if (!item.privacy || !hasString(item.privacy.classification)) {
|
|
315
|
-
errors.push("items[" + index + "].privacy.classification is required");
|
|
316
|
-
}
|
|
317
1158
|
}
|
|
318
1159
|
return errors;
|
|
319
1160
|
}
|
|
320
1161
|
function validateAiwgFortemiChunkPart(value, partRef, manifest) {
|
|
321
1162
|
const errors = [];
|
|
322
|
-
const data = value;
|
|
1163
|
+
const data = isPlainRecord(value) ? value : {};
|
|
1164
|
+
if (!isPlainRecord(value)) errors.push("chunk part must be an object");
|
|
323
1165
|
if (data?.schema_version !== "aiwg.fortemi.index.chunk.v1") {
|
|
324
1166
|
errors.push("schema_version must be aiwg.fortemi.index.chunk.v1");
|
|
325
1167
|
}
|
|
@@ -336,12 +1178,16 @@ function validateAiwgFortemiChunkPart(value, partRef, manifest) {
|
|
|
336
1178
|
}
|
|
337
1179
|
if (Array.isArray(data?.items)) {
|
|
338
1180
|
if (manifest?.projection) {
|
|
339
|
-
errors.push(...validateProjectedRecords(
|
|
1181
|
+
errors.push(...validateProjectedRecords(
|
|
1182
|
+
data.items,
|
|
1183
|
+
manifest.source_export_schema_version ?? "aiwg.fortemi.index.export.v1"
|
|
1184
|
+
).map((error) => "items." + error));
|
|
340
1185
|
} else {
|
|
341
1186
|
const validation = validateAiwgFortemiIndexExport({
|
|
342
|
-
schema_version: "aiwg.fortemi.index.export.v1",
|
|
1187
|
+
schema_version: manifest?.source_export_schema_version ?? "aiwg.fortemi.index.export.v1",
|
|
343
1188
|
generated_at: manifest?.generated_at ?? "1970-01-01T00:00:00.000Z",
|
|
344
1189
|
source: manifest?.source ?? { repo: "chunk", privacy: "public" },
|
|
1190
|
+
...(manifest?.source_export_schema_version ?? "aiwg.fortemi.index.export.v1") === "aiwg.fortemi.index.export.v2" ? { compatibility: { previous_schema_version: "aiwg.fortemi.index.export.v1", strategy: "supported" } } : {},
|
|
345
1191
|
items: data.items
|
|
346
1192
|
});
|
|
347
1193
|
errors.push(...validation.errors.map((error) => "items." + error));
|
|
@@ -356,9 +1202,35 @@ function assertAiwgFortemiChunkPart(value, partRef, manifest) {
|
|
|
356
1202
|
}
|
|
357
1203
|
return value;
|
|
358
1204
|
}
|
|
1205
|
+
var ALLOWED_AIWG_FETCH_SCHEMES = /* @__PURE__ */ new Set(["http:", "https:", "blob:", "data:"]);
|
|
1206
|
+
function tryParseUrl(value) {
|
|
1207
|
+
try {
|
|
1208
|
+
return new URL(value);
|
|
1209
|
+
} catch {
|
|
1210
|
+
return null;
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
function resolveAiwgFetchUrl(href, baseUrl) {
|
|
1214
|
+
if (baseUrl === void 0) {
|
|
1215
|
+
const absolute = tryParseUrl(href);
|
|
1216
|
+
if (absolute && !ALLOWED_AIWG_FETCH_SCHEMES.has(absolute.protocol)) {
|
|
1217
|
+
throw new Error("Refusing AIWG index fetch with disallowed scheme: " + absolute.protocol);
|
|
1218
|
+
}
|
|
1219
|
+
return href;
|
|
1220
|
+
}
|
|
1221
|
+
const base = new URL(baseUrl);
|
|
1222
|
+
const resolved = new URL(href, base);
|
|
1223
|
+
if (!ALLOWED_AIWG_FETCH_SCHEMES.has(resolved.protocol)) {
|
|
1224
|
+
throw new Error("Refusing AIWG index fetch with disallowed scheme: " + resolved.protocol);
|
|
1225
|
+
}
|
|
1226
|
+
if (resolved.origin !== base.origin) {
|
|
1227
|
+
throw new Error("Refusing cross-origin AIWG index fetch: " + resolved.origin + " != " + base.origin);
|
|
1228
|
+
}
|
|
1229
|
+
return resolved.toString();
|
|
1230
|
+
}
|
|
359
1231
|
function createAiwgFetchChunkLoader(baseUrl) {
|
|
360
1232
|
return async (part) => {
|
|
361
|
-
const href =
|
|
1233
|
+
const href = resolveAiwgFetchUrl(part.href, baseUrl);
|
|
362
1234
|
const response = await fetch(href);
|
|
363
1235
|
if (!response.ok) throw new Error("Failed to fetch AIWG index chunk " + href + ": " + response.status);
|
|
364
1236
|
return response.json();
|
|
@@ -378,14 +1250,14 @@ function createAiwgFetchDetailLoader(baseUrl) {
|
|
|
378
1250
|
return async (id, manifest) => {
|
|
379
1251
|
if (!manifest.detail?.href) throw new Error("Manifest has no detail.href for record resolution");
|
|
380
1252
|
const relative = aiwgDetailHrefForId(manifest.detail, id);
|
|
381
|
-
const href =
|
|
1253
|
+
const href = resolveAiwgFetchUrl(relative, baseUrl);
|
|
382
1254
|
const response = await fetch(href);
|
|
383
1255
|
if (!response.ok) throw new Error("Failed to fetch AIWG index detail " + href + ": " + response.status);
|
|
384
1256
|
return response.json();
|
|
385
1257
|
};
|
|
386
1258
|
}
|
|
387
1259
|
function getAiwgFortemiFacets(items) {
|
|
388
|
-
const result =
|
|
1260
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
389
1261
|
for (const item of items) {
|
|
390
1262
|
pushFacet(result, "type", item.type);
|
|
391
1263
|
pushFacet(result, "privacy", item.privacy.classification);
|
|
@@ -402,15 +1274,17 @@ function recordTitle(item) {
|
|
|
402
1274
|
}
|
|
403
1275
|
function recordText(item) {
|
|
404
1276
|
const base = item.text ?? item.search?.body ?? item.search?.summary ?? item.chunks?.map((chunk) => chunk.text ?? chunk.body ?? chunk.summary ?? "").filter(Boolean).join("\n") ?? "";
|
|
405
|
-
const extractedText =
|
|
1277
|
+
const extractedText = binarySourceText(item);
|
|
406
1278
|
return [base, extractedText].filter(Boolean).join("\n");
|
|
407
1279
|
}
|
|
1280
|
+
function binarySourceText(item) {
|
|
1281
|
+
return "binary_sources" in item ? item.binary_sources?.map((source) => source.extracted_text).filter(Boolean).join("\n") ?? "" : "";
|
|
1282
|
+
}
|
|
408
1283
|
function defaultEmbeddingInput(record, granularity) {
|
|
409
1284
|
const title = recordTitle(record);
|
|
410
1285
|
const text = recordText(record);
|
|
411
1286
|
if (granularity === "title-summary") {
|
|
412
|
-
|
|
413
|
-
return [title, record.search?.summary ?? "", extractedText].filter(Boolean).join("\n");
|
|
1287
|
+
return [title, record.search?.summary ?? "", binarySourceText(record)].filter(Boolean).join("\n");
|
|
414
1288
|
}
|
|
415
1289
|
return [title, text].filter(Boolean).join("\n");
|
|
416
1290
|
}
|
|
@@ -421,7 +1295,7 @@ function generatedAtString(value) {
|
|
|
421
1295
|
async function buildAiwgStaticEmbeddingSet(index, options) {
|
|
422
1296
|
assertAiwgFortemiIndexExport(index);
|
|
423
1297
|
const granularity = options.granularity ?? "body";
|
|
424
|
-
const records = options.records ?? index.items;
|
|
1298
|
+
const records = filterAiwgRecordsByPrivacy(options.records ?? index.items, options.privacy);
|
|
425
1299
|
const embeddings = [];
|
|
426
1300
|
for (const record of records) {
|
|
427
1301
|
const input = options.textForRecord?.(record) ?? defaultEmbeddingInput(record, granularity);
|
|
@@ -478,11 +1352,12 @@ function recordSearchValues(item) {
|
|
|
478
1352
|
return values.filter((value) => typeof value === "string" && value.length > 0);
|
|
479
1353
|
}
|
|
480
1354
|
function buildAiwgChunkedIndex(index, options = {}) {
|
|
1355
|
+
assertAiwgFortemiIndexExport(index);
|
|
481
1356
|
const partSize = hasPositiveInteger(options.partSize) ? options.partSize : 500;
|
|
482
1357
|
const projection = options.projection;
|
|
483
1358
|
const idEncoding = options.idEncoding ?? "base64url";
|
|
484
1359
|
const detailHref = options.detailHref ?? "detail/{id}.json";
|
|
485
|
-
const items = index.items;
|
|
1360
|
+
const items = filterAiwgRecordsByPrivacy(index.items, options.privacy);
|
|
486
1361
|
const pad = (value) => String(value).padStart(4, "0");
|
|
487
1362
|
const project = (record) => {
|
|
488
1363
|
if (!projection) return record;
|
|
@@ -510,6 +1385,7 @@ function buildAiwgChunkedIndex(index, options = {}) {
|
|
|
510
1385
|
schema_version: "aiwg.fortemi.index.chunk-manifest.v1",
|
|
511
1386
|
generated_at: options.generatedAt ?? index.generated_at,
|
|
512
1387
|
source: index.source,
|
|
1388
|
+
source_export_schema_version: index.schema_version,
|
|
513
1389
|
total: items.length,
|
|
514
1390
|
part_size: partSize,
|
|
515
1391
|
facets: getAiwgFortemiFacets(items),
|
|
@@ -556,35 +1432,88 @@ function queryMatches(item, q) {
|
|
|
556
1432
|
return matches;
|
|
557
1433
|
}
|
|
558
1434
|
var DISCOVERY_STOPWORDS = /* @__PURE__ */ new Set([
|
|
1435
|
+
"the",
|
|
559
1436
|
"a",
|
|
560
1437
|
"an",
|
|
561
1438
|
"and",
|
|
562
|
-
"
|
|
563
|
-
"
|
|
1439
|
+
"or",
|
|
1440
|
+
"of",
|
|
564
1441
|
"for",
|
|
565
|
-
"
|
|
566
|
-
"how",
|
|
567
|
-
"i",
|
|
1442
|
+
"to",
|
|
568
1443
|
"in",
|
|
1444
|
+
"on",
|
|
1445
|
+
"with",
|
|
1446
|
+
"into",
|
|
1447
|
+
"from",
|
|
569
1448
|
"is",
|
|
1449
|
+
"are",
|
|
1450
|
+
"be",
|
|
1451
|
+
"i",
|
|
1452
|
+
"we",
|
|
1453
|
+
"my",
|
|
1454
|
+
"it",
|
|
1455
|
+
"you",
|
|
570
1456
|
"me",
|
|
571
|
-
"
|
|
572
|
-
"
|
|
573
|
-
"
|
|
1457
|
+
"us",
|
|
1458
|
+
"your",
|
|
1459
|
+
"our",
|
|
1460
|
+
"this",
|
|
1461
|
+
"that",
|
|
1462
|
+
"these",
|
|
1463
|
+
"those",
|
|
1464
|
+
"there",
|
|
1465
|
+
"here",
|
|
1466
|
+
"some",
|
|
1467
|
+
"any",
|
|
1468
|
+
"all",
|
|
1469
|
+
"also",
|
|
574
1470
|
"please",
|
|
575
|
-
"
|
|
576
|
-
"
|
|
577
|
-
"
|
|
578
|
-
"
|
|
1471
|
+
"about",
|
|
1472
|
+
"how",
|
|
1473
|
+
"what",
|
|
1474
|
+
"which",
|
|
1475
|
+
"where",
|
|
1476
|
+
"when",
|
|
1477
|
+
"who",
|
|
1478
|
+
"why",
|
|
1479
|
+
"find",
|
|
1480
|
+
"give",
|
|
1481
|
+
"show",
|
|
1482
|
+
"need",
|
|
1483
|
+
"want",
|
|
1484
|
+
"looking",
|
|
1485
|
+
"look",
|
|
1486
|
+
"help",
|
|
1487
|
+
"do",
|
|
1488
|
+
"does",
|
|
1489
|
+
"did",
|
|
1490
|
+
"can",
|
|
1491
|
+
"could",
|
|
1492
|
+
"should",
|
|
1493
|
+
"would",
|
|
1494
|
+
"will",
|
|
1495
|
+
"handle",
|
|
1496
|
+
"handles",
|
|
1497
|
+
"handling",
|
|
1498
|
+
"aiwg",
|
|
1499
|
+
"skill",
|
|
1500
|
+
"skills",
|
|
1501
|
+
"agent",
|
|
1502
|
+
"agents",
|
|
1503
|
+
"command",
|
|
1504
|
+
"commands",
|
|
1505
|
+
"rule",
|
|
1506
|
+
"rules",
|
|
1507
|
+
"flow",
|
|
1508
|
+
"flows",
|
|
1509
|
+
"workflow",
|
|
1510
|
+
"workflows"
|
|
579
1511
|
]);
|
|
580
|
-
function
|
|
581
|
-
return value.toLowerCase().replace(/[_
|
|
582
|
-
}
|
|
583
|
-
function canonicalDiscoveryName(value) {
|
|
584
|
-
return normalizeDiscoveryText(value).replace(/[\s.-]+/g, "");
|
|
1512
|
+
function normalizeDiscoveryName(value) {
|
|
1513
|
+
return value.toLowerCase().replace(/[-_\s]+/g, " ").trim();
|
|
585
1514
|
}
|
|
586
1515
|
function discoveryTokens(value) {
|
|
587
|
-
return
|
|
1516
|
+
return value.toLowerCase().split(/[^a-z0-9-]+/).filter((token) => token.length > 1 && !DISCOVERY_STOPWORDS.has(token));
|
|
588
1517
|
}
|
|
589
1518
|
function facetValues(item, names) {
|
|
590
1519
|
return names.flatMap((name) => item.facets[name] ?? []);
|
|
@@ -594,17 +1523,58 @@ function addDiscoveryMatch(matches, match) {
|
|
|
594
1523
|
matches.push(match);
|
|
595
1524
|
}
|
|
596
1525
|
}
|
|
597
|
-
function
|
|
598
|
-
if (
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
1526
|
+
function damerauLevenshteinAtMostOne(left, right) {
|
|
1527
|
+
if (left === right) return true;
|
|
1528
|
+
if (Math.abs(left.length - right.length) > 1) return false;
|
|
1529
|
+
if (left.length === right.length) {
|
|
1530
|
+
let firstDiff = -1;
|
|
1531
|
+
let diffCount = 0;
|
|
1532
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
1533
|
+
if (left[index] !== right[index]) {
|
|
1534
|
+
if (firstDiff < 0) firstDiff = index;
|
|
1535
|
+
diffCount += 1;
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
if (diffCount === 1) return true;
|
|
1539
|
+
return diffCount === 2 && firstDiff + 1 < left.length && left[firstDiff] === right[firstDiff + 1] && left[firstDiff + 1] === right[firstDiff];
|
|
1540
|
+
}
|
|
1541
|
+
const shorter = left.length < right.length ? left : right;
|
|
1542
|
+
const longer = left.length < right.length ? right : left;
|
|
1543
|
+
let shorterIndex = 0;
|
|
1544
|
+
let longerIndex = 0;
|
|
1545
|
+
let edits = 0;
|
|
1546
|
+
while (shorterIndex < shorter.length && longerIndex < longer.length) {
|
|
1547
|
+
if (shorter[shorterIndex] === longer[longerIndex]) {
|
|
1548
|
+
shorterIndex += 1;
|
|
1549
|
+
longerIndex += 1;
|
|
1550
|
+
} else {
|
|
1551
|
+
edits += 1;
|
|
1552
|
+
if (edits > 1) return false;
|
|
1553
|
+
longerIndex += 1;
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
return true;
|
|
1557
|
+
}
|
|
1558
|
+
function nearDiscoveryNameMatch(query, name) {
|
|
1559
|
+
const queryParts = normalizeDiscoveryName(query).split(/\s+/).filter(Boolean);
|
|
1560
|
+
const nameParts = normalizeDiscoveryName(name).split(/\s+/).filter(Boolean);
|
|
1561
|
+
if (queryParts.length !== nameParts.length) return false;
|
|
1562
|
+
return queryParts.every((part, index) => {
|
|
1563
|
+
const target = nameParts[index];
|
|
1564
|
+
if (part === target) return true;
|
|
1565
|
+
if (part.length < 5 || target.length < 5) return false;
|
|
1566
|
+
return damerauLevenshteinAtMostOne(part, target);
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
function lowerValues(values) {
|
|
1570
|
+
return values.map((value) => value.toLowerCase());
|
|
602
1571
|
}
|
|
603
|
-
function discoveryMatches(item, query) {
|
|
1572
|
+
function discoveryMatches(item, query, options = {}) {
|
|
604
1573
|
if (!query) return [];
|
|
605
1574
|
const matches = [];
|
|
606
1575
|
const tokens = discoveryTokens(query);
|
|
607
|
-
const
|
|
1576
|
+
const lower = tokens.length > 0 ? tokens.join(" ") : query.toLowerCase().trim();
|
|
1577
|
+
const rawLower = query.toLowerCase().trim();
|
|
608
1578
|
const idParts = item.id.split(/[:/]/);
|
|
609
1579
|
const names = [
|
|
610
1580
|
item.id,
|
|
@@ -630,33 +1600,66 @@ function discoveryMatches(item, query) {
|
|
|
630
1600
|
...item.tags
|
|
631
1601
|
].filter((value) => hasString(value));
|
|
632
1602
|
const sourceValues = [item.source?.path, item.source?.repo_relative_path, item.source?.locator].filter((value) => hasString(value));
|
|
1603
|
+
const title = recordTitle(item);
|
|
1604
|
+
const summary = [item.search?.summary, recordText(item)].filter((value) => hasString(value)).join("\n");
|
|
1605
|
+
const type = item.search?.type ?? item.type;
|
|
1606
|
+
const tags = [...item.search?.tags ?? [], ...item.tags];
|
|
1607
|
+
const useMultiToken = tokens.length > 1;
|
|
1608
|
+
const minHits = useMultiToken ? options.relaxOverlap ? 1 : Math.ceil(tokens.length / 2) : 1;
|
|
1609
|
+
const overlapOK = (hits) => useMultiToken && hits >= minHits;
|
|
1610
|
+
const addInfo = (match) => addDiscoveryMatch(matches, { ...match, score: 0 });
|
|
1611
|
+
const addScore = (score2) => {
|
|
1612
|
+
if (score2 > 0) addDiscoveryMatch(matches, { field: "id", value: item.id, score: score2, reason: "aiwg discovery score" });
|
|
1613
|
+
};
|
|
633
1614
|
for (const name of names) {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
}
|
|
639
|
-
|
|
1615
|
+
if (normalizeDiscoveryName(query) === normalizeDiscoveryName(name)) {
|
|
1616
|
+
addInfo({ field: "id", value: name, reason: "exact canonical name" });
|
|
1617
|
+
addScore(1.001);
|
|
1618
|
+
return matches;
|
|
1619
|
+
}
|
|
1620
|
+
if (nearDiscoveryNameMatch(query, name)) {
|
|
1621
|
+
addInfo({ field: "id", value: name, reason: "near canonical name" });
|
|
1622
|
+
addScore(0.951);
|
|
1623
|
+
return matches;
|
|
640
1624
|
}
|
|
641
1625
|
}
|
|
642
|
-
|
|
643
|
-
const
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
1626
|
+
let score = 0;
|
|
1627
|
+
const scoreText = (field, value, reason, exactScore, overlapScore, weight, exactBonus = 0) => {
|
|
1628
|
+
const normalized = value.toLowerCase();
|
|
1629
|
+
if (normalized.includes(lower)) {
|
|
1630
|
+
score += exactScore * weight;
|
|
1631
|
+
if (normalized === lower) score += exactBonus;
|
|
1632
|
+
addInfo({ field, value, reason });
|
|
1633
|
+
} else if (useMultiToken) {
|
|
1634
|
+
const hits = tokens.filter((token) => normalized.includes(token)).length;
|
|
1635
|
+
if (overlapOK(hits)) {
|
|
1636
|
+
score += overlapScore * weight * (hits / tokens.length);
|
|
1637
|
+
addInfo({ field, value, reason });
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
};
|
|
1641
|
+
for (const trigger of lowerValues(triggers)) {
|
|
1642
|
+
if (trigger === lower || trigger === rawLower) {
|
|
1643
|
+
addInfo({ field: "facet", value: trigger, reason: "trigger phrase" });
|
|
1644
|
+
addScore(1.0008);
|
|
1645
|
+
return matches;
|
|
1646
|
+
}
|
|
648
1647
|
}
|
|
649
|
-
for (const
|
|
650
|
-
|
|
651
|
-
if (overlap > 0) addDiscoveryMatch(matches, { field: "concept", value: capability, score: 22 * overlap, reason: "capability overlap" });
|
|
1648
|
+
for (const trigger of triggers) {
|
|
1649
|
+
scoreText("facet", trigger, "trigger phrase", 0.25, 0.06, 4);
|
|
652
1650
|
}
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
|
|
1651
|
+
for (const capability of capabilities) scoreText("concept", capability, "capability overlap", 0.2, 0.1, 2);
|
|
1652
|
+
scoreText("title", title, "title token overlap", 0.3, 0.08, 3, 0.2);
|
|
1653
|
+
for (const tag of tags) scoreText("tag", tag, "tag token overlap", 0.2, 0.05, 2);
|
|
1654
|
+
if (summary) scoreText("text", summary, "body token overlap", 0.15, 0.04, 1);
|
|
656
1655
|
for (const source of sourceValues) {
|
|
657
|
-
|
|
658
|
-
|
|
1656
|
+
scoreText("source", source, "path overlap", 0.1, 0.03, 1);
|
|
1657
|
+
}
|
|
1658
|
+
if (type.toLowerCase().includes(lower)) {
|
|
1659
|
+
score += 0.1;
|
|
1660
|
+
addInfo({ field: "facet", value: type, reason: "type overlap" });
|
|
659
1661
|
}
|
|
1662
|
+
addScore(Math.min(score, 1));
|
|
660
1663
|
return matches;
|
|
661
1664
|
}
|
|
662
1665
|
function rankMatches(matches, weights) {
|
|
@@ -682,13 +1685,13 @@ function createSnippet(item, matches, q, maxLength) {
|
|
|
682
1685
|
const firstMatch = textMatch ?? titleMatch ?? matches[0];
|
|
683
1686
|
return clipSnippet(firstMatch?.value ?? recordText(item), q, maxLength);
|
|
684
1687
|
}
|
|
685
|
-
function createRankedEntries(items, q, options, ordinalBase = 0) {
|
|
1688
|
+
function createRankedEntries(items, q, options, ordinalBase = 0, discoveryOptions = {}) {
|
|
686
1689
|
const weights = { ...DEFAULT_QUERY_WEIGHTS, ...options.weights };
|
|
687
1690
|
const profile = options.searchProfile ?? "default";
|
|
688
1691
|
return items.map((item, ordinal) => ({
|
|
689
1692
|
item,
|
|
690
1693
|
ordinal: ordinalBase + ordinal,
|
|
691
|
-
matches: profile === "aiwg-discovery" ? discoveryMatches(item, q) : queryMatches(item, q)
|
|
1694
|
+
matches: profile === "aiwg-discovery" ? discoveryMatches(item, q, discoveryOptions) : queryMatches(item, q)
|
|
692
1695
|
})).filter(({ item, matches }) => {
|
|
693
1696
|
if (q && matches.length === 0) return false;
|
|
694
1697
|
if (options.types && !options.types.includes(item.type)) return false;
|
|
@@ -738,8 +1741,7 @@ function queryAiwgFortemiIndex(index, query = "", options = {}) {
|
|
|
738
1741
|
const q = query.trim().toLowerCase();
|
|
739
1742
|
const entries = createRankedEntries(index.items, q, options);
|
|
740
1743
|
if (entries.length === 0 && q && options.searchProfile === "aiwg-discovery") {
|
|
741
|
-
|
|
742
|
-
return createQueryResultFromRankedEntries(createRankedEntries(index.items, relaxed, options), relaxed, options);
|
|
1744
|
+
return createQueryResultFromRankedEntries(createRankedEntries(index.items, q, options, 0, { relaxOverlap: true }), q, options);
|
|
743
1745
|
}
|
|
744
1746
|
return createQueryResultFromRankedEntries(entries, q, options);
|
|
745
1747
|
}
|
|
@@ -760,7 +1762,8 @@ function cosineSimilarity(left, right) {
|
|
|
760
1762
|
}
|
|
761
1763
|
function validateAiwgStaticEmbeddingSet(value) {
|
|
762
1764
|
const errors = [];
|
|
763
|
-
const data = value;
|
|
1765
|
+
const data = isPlainRecord(value) ? value : {};
|
|
1766
|
+
if (!isPlainRecord(value)) errors.push("embedding set must be an object");
|
|
764
1767
|
if (data?.schema_version !== "aiwg.fortemi.embedding.set.v1") errors.push("schema_version must be aiwg.fortemi.embedding.set.v1");
|
|
765
1768
|
if (!hasString(data?.id)) errors.push("id is required");
|
|
766
1769
|
if (!hasString(data?.model)) errors.push("model is required");
|
|
@@ -768,7 +1771,12 @@ function validateAiwgStaticEmbeddingSet(value) {
|
|
|
768
1771
|
if (!hasString(data?.generated_at)) errors.push("generated_at is required");
|
|
769
1772
|
if (!hasString(data?.granularity)) errors.push("granularity is required");
|
|
770
1773
|
if (!Array.isArray(data?.embeddings)) errors.push("embeddings must be an array");
|
|
771
|
-
|
|
1774
|
+
const embeddings = Array.isArray(data.embeddings) ? data.embeddings : [];
|
|
1775
|
+
for (const [index, embedding] of embeddings.entries()) {
|
|
1776
|
+
if (!isPlainRecord(embedding)) {
|
|
1777
|
+
errors.push("embeddings[" + index + "] must be an object");
|
|
1778
|
+
continue;
|
|
1779
|
+
}
|
|
772
1780
|
if (!hasString(embedding.record_id)) errors.push("embeddings[" + index + "].record_id is required");
|
|
773
1781
|
if (!hasString(embedding.input_hash)) errors.push("embeddings[" + index + "].input_hash is required");
|
|
774
1782
|
if (!Array.isArray(embedding.embedding)) errors.push("embeddings[" + index + "].embedding must be an array");
|
|
@@ -798,7 +1806,10 @@ function queryAiwgSemanticIndex(index, embeddingSet, queryEmbedding, options = {
|
|
|
798
1806
|
}).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);
|
|
799
1807
|
}
|
|
800
1808
|
function queryAiwgHybridIndex(index, embeddingSet, query, queryEmbedding, options = {}) {
|
|
801
|
-
const
|
|
1809
|
+
const lexicalOptions = { ...options };
|
|
1810
|
+
delete lexicalOptions.limit;
|
|
1811
|
+
delete lexicalOptions.offset;
|
|
1812
|
+
const lexical = queryAiwgFortemiIndex(index, query, { ...lexicalOptions, rank: true });
|
|
802
1813
|
const semantic = queryAiwgSemanticIndex(index, embeddingSet, queryEmbedding, { limit: index.items.length });
|
|
803
1814
|
const lexicalWeight = options.lexicalWeight ?? 0.5;
|
|
804
1815
|
const semanticWeight = options.semanticWeight ?? 0.5;
|
|
@@ -820,8 +1831,15 @@ function queryAiwgHybridIndex(index, embeddingSet, query, queryEmbedding, option
|
|
|
820
1831
|
};
|
|
821
1832
|
}).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
1833
|
}
|
|
823
|
-
|
|
1834
|
+
var DEFAULT_AIWG_DUPLICATE_SCAN_MAX_EMBEDDINGS = 5e3;
|
|
1835
|
+
function findAiwgStaticDuplicatePairs(index, embeddingSet, threshold = 0.9, options) {
|
|
824
1836
|
assertAiwgStaticEmbeddingSet(embeddingSet);
|
|
1837
|
+
const maxEmbeddings = options?.maxEmbeddings ?? DEFAULT_AIWG_DUPLICATE_SCAN_MAX_EMBEDDINGS;
|
|
1838
|
+
if (embeddingSet.embeddings.length > maxEmbeddings) {
|
|
1839
|
+
throw new Error(
|
|
1840
|
+
"Embedding set too large for duplicate scan: " + embeddingSet.embeddings.length + " > " + maxEmbeddings + " (raise options.maxEmbeddings to override for trusted input)"
|
|
1841
|
+
);
|
|
1842
|
+
}
|
|
825
1843
|
const byId = new Map(index.items.map((item) => [item.id, item]));
|
|
826
1844
|
const pairs = [];
|
|
827
1845
|
for (let leftIndex = 0; leftIndex < embeddingSet.embeddings.length; leftIndex += 1) {
|
|
@@ -861,6 +1879,7 @@ function matchSetCacheKey(q, options) {
|
|
|
861
1879
|
concepts: options.concepts ?? null,
|
|
862
1880
|
privacy: options.privacy ?? null,
|
|
863
1881
|
rel: options.relationshipTargetId ?? null,
|
|
1882
|
+
searchProfile: options.searchProfile ?? "default",
|
|
864
1883
|
weights: { ...DEFAULT_QUERY_WEIGHTS, ...options.weights }
|
|
865
1884
|
});
|
|
866
1885
|
}
|
|
@@ -949,15 +1968,21 @@ function edgeFromRelationship(sourceId, relationship) {
|
|
|
949
1968
|
}
|
|
950
1969
|
function relationshipMatches(edge, options = {}) {
|
|
951
1970
|
const type = relationshipTypeFilter(options);
|
|
952
|
-
const direction = options.direction ?? "both";
|
|
953
1971
|
if (type && edge.type !== type) return false;
|
|
954
1972
|
if (options.relationshipDirection && edge.direction !== options.relationshipDirection) return false;
|
|
955
1973
|
if (options.sourceId && edge.source_id !== options.sourceId) return false;
|
|
956
1974
|
if (options.targetId && edge.target_id !== options.targetId) return false;
|
|
957
|
-
if (
|
|
958
|
-
if (direction === "in" && options.sourceId && edge.source_id !== options.sourceId) return false;
|
|
1975
|
+
if (options.endpointId && edge.source_id !== options.endpointId && edge.target_id !== options.endpointId) return false;
|
|
959
1976
|
return true;
|
|
960
1977
|
}
|
|
1978
|
+
function assertRelationshipDirectionAnchor(options) {
|
|
1979
|
+
if (options.direction === "out" && !options.sourceId) {
|
|
1980
|
+
throw new Error("relationship direction 'out' requires sourceId");
|
|
1981
|
+
}
|
|
1982
|
+
if (options.direction === "in" && !options.targetId) {
|
|
1983
|
+
throw new Error("relationship direction 'in' requires targetId");
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
961
1986
|
function nodeSummary(item) {
|
|
962
1987
|
return { id: item.id, type: item.type, title: recordTitle(item) };
|
|
963
1988
|
}
|
|
@@ -965,6 +1990,7 @@ function addNode(nodes, item) {
|
|
|
965
1990
|
if (item) nodes.set(item.id, nodeSummary(item));
|
|
966
1991
|
}
|
|
967
1992
|
function relationshipResultFromRecords(records, options = {}) {
|
|
1993
|
+
assertRelationshipDirectionAnchor(options);
|
|
968
1994
|
const byId = new Map(records.map((record) => [record.id, record]));
|
|
969
1995
|
const edges = [];
|
|
970
1996
|
for (const record of records) {
|
|
@@ -974,7 +2000,8 @@ function relationshipResultFromRecords(records, options = {}) {
|
|
|
974
2000
|
edges.push(edge);
|
|
975
2001
|
}
|
|
976
2002
|
}
|
|
977
|
-
const
|
|
2003
|
+
const sortedEdges = edges.sort((left, right) => left.source_id.localeCompare(right.source_id) || left.target_id.localeCompare(right.target_id) || left.type.localeCompare(right.type));
|
|
2004
|
+
const limitedEdges = options.limit ? sortedEdges.slice(0, options.limit) : sortedEdges;
|
|
978
2005
|
const nodes = /* @__PURE__ */ new Map();
|
|
979
2006
|
for (const edge of limitedEdges) {
|
|
980
2007
|
addNode(nodes, byId.get(edge.source_id));
|
|
@@ -991,7 +2018,8 @@ function neighborQueryOptions(id, options = {}) {
|
|
|
991
2018
|
return {
|
|
992
2019
|
...options,
|
|
993
2020
|
...direction === "out" ? { sourceId: id } : {},
|
|
994
|
-
...direction === "in" ? { targetId: id } : {}
|
|
2021
|
+
...direction === "in" ? { targetId: id } : {},
|
|
2022
|
+
...direction === "both" ? { endpointId: id } : {}
|
|
995
2023
|
};
|
|
996
2024
|
}
|
|
997
2025
|
function filterNeighborResult(id, result, options = {}) {
|
|
@@ -1286,7 +2314,7 @@ function createAiwgIndexController(initialIndex) {
|
|
|
1286
2314
|
notify();
|
|
1287
2315
|
},
|
|
1288
2316
|
createReviewDecisionExport(generatedAt) {
|
|
1289
|
-
const source = index ?? (chunked ? { schema_version: "aiwg.fortemi.index.export.v1" } : null);
|
|
2317
|
+
const source = index ?? (chunked ? { schema_version: chunked.manifest.source_export_schema_version ?? "aiwg.fortemi.index.export.v1" } : null);
|
|
1290
2318
|
if (!source) throw new Error("No AIWG index export or chunked manifest loaded");
|
|
1291
2319
|
return createAiwgReviewDecisionExport(source, reviewDecisions, generatedAt);
|
|
1292
2320
|
},
|
|
@@ -1300,13 +2328,14 @@ function createAiwgIndexController(initialIndex) {
|
|
|
1300
2328
|
}
|
|
1301
2329
|
function aiwgFortemiIndexToCommunityGraph(index, options = {}) {
|
|
1302
2330
|
const ids = new Set(index.items.map((item) => item.id));
|
|
1303
|
-
const relationshipWeights = options.relationshipWeights ??
|
|
2331
|
+
const relationshipWeights = options.relationshipWeights ?? /* @__PURE__ */ Object.create(null);
|
|
1304
2332
|
const edgeCounts = /* @__PURE__ */ new Map();
|
|
1305
2333
|
for (const item of index.items) {
|
|
1306
2334
|
for (const relationship of item.relationships) {
|
|
1307
2335
|
if (!ids.has(relationship.target_id) && !options.includeDanglingRelationships) continue;
|
|
1308
2336
|
const kind = relationship.type;
|
|
1309
|
-
const
|
|
2337
|
+
const configuredWeight = Object.prototype.hasOwnProperty.call(relationshipWeights, kind) ? relationshipWeights[kind] : void 0;
|
|
2338
|
+
const baseWeight = typeof configuredWeight === "number" && Number.isFinite(configuredWeight) ? configuredWeight : 1;
|
|
1310
2339
|
const key = `${item.id}\0${relationship.target_id}\0${kind}`;
|
|
1311
2340
|
const existing = edgeCounts.get(key);
|
|
1312
2341
|
if (existing) existing.weight += baseWeight;
|
|
@@ -1342,6 +2371,6 @@ function communityIdsFor(item, options) {
|
|
|
1342
2371
|
return [`type:${item.type}`];
|
|
1343
2372
|
}
|
|
1344
2373
|
|
|
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 };
|
|
2374
|
+
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
2375
|
//# sourceMappingURL=aiwg-index.js.map
|
|
1347
2376
|
//# sourceMappingURL=aiwg-index.js.map
|