@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/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { v7 } from 'uuid';
|
|
2
|
-
import { PGlite } from '@electric-sql/pglite';
|
|
3
|
-
import { vector } from '@electric-sql/pglite/vector';
|
|
4
2
|
import { sha256 } from '@noble/hashes/sha256';
|
|
3
|
+
import { blake3 } from '@noble/hashes/blake3';
|
|
5
4
|
import { bytesToHex } from '@noble/hashes/utils';
|
|
6
5
|
import { z } from 'zod';
|
|
7
6
|
import { gzipSync, gunzipSync } from 'fflate';
|
|
7
|
+
import Ajv2020 from 'ajv/dist/2020.js';
|
|
8
8
|
|
|
9
9
|
// src/uuid.ts
|
|
10
10
|
function generateId() {
|
|
@@ -125,6 +125,8 @@ var TypedEventBus = class {
|
|
|
125
125
|
this.wildcardListeners.clear();
|
|
126
126
|
}
|
|
127
127
|
};
|
|
128
|
+
|
|
129
|
+
// src/db.ts
|
|
128
130
|
function getDataDir(persistence, archiveName) {
|
|
129
131
|
switch (persistence) {
|
|
130
132
|
case "opfs":
|
|
@@ -137,6 +139,10 @@ function getDataDir(persistence, archiveName) {
|
|
|
137
139
|
}
|
|
138
140
|
async function createPGliteInstance(persistence, archiveName = "default", options = {}) {
|
|
139
141
|
const dataDir = getDataDir(persistence, archiveName);
|
|
142
|
+
const [{ PGlite }, { vector }] = await Promise.all([
|
|
143
|
+
import('@electric-sql/pglite'),
|
|
144
|
+
import('@electric-sql/pglite/vector')
|
|
145
|
+
]);
|
|
140
146
|
const pgliteOptions = {
|
|
141
147
|
database: "postgres",
|
|
142
148
|
// PGlite 0.4.x breaking change: explicit required
|
|
@@ -609,6 +615,134 @@ var migration0010 = {
|
|
|
609
615
|
`
|
|
610
616
|
};
|
|
611
617
|
|
|
618
|
+
// src/migrations/0011_embedding_member_metadata.ts
|
|
619
|
+
var migration0011 = {
|
|
620
|
+
version: 11,
|
|
621
|
+
name: "0011_embedding_member_metadata",
|
|
622
|
+
sql: `
|
|
623
|
+
ALTER TABLE embedding_set_member
|
|
624
|
+
ALTER COLUMN embedding_id DROP NOT NULL;
|
|
625
|
+
|
|
626
|
+
ALTER TABLE embedding_set_member
|
|
627
|
+
ADD COLUMN IF NOT EXISTS membership_type TEXT NOT NULL DEFAULT 'materialized';
|
|
628
|
+
|
|
629
|
+
ALTER TABLE embedding_set_member
|
|
630
|
+
ADD COLUMN IF NOT EXISTS added_at TIMESTAMPTZ NOT NULL DEFAULT now();
|
|
631
|
+
|
|
632
|
+
ALTER TABLE embedding_set_member
|
|
633
|
+
ADD COLUMN IF NOT EXISTS added_by TEXT;
|
|
634
|
+
`
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
// src/migrations/0012_embedding_configs.ts
|
|
638
|
+
var migration0012 = {
|
|
639
|
+
version: 12,
|
|
640
|
+
name: "0012_embedding_configs",
|
|
641
|
+
sql: `
|
|
642
|
+
CREATE TABLE IF NOT EXISTS embedding_config (
|
|
643
|
+
id TEXT PRIMARY KEY,
|
|
644
|
+
name TEXT NOT NULL,
|
|
645
|
+
description TEXT,
|
|
646
|
+
model TEXT NOT NULL,
|
|
647
|
+
dimension INTEGER NOT NULL,
|
|
648
|
+
chunk_size INTEGER NOT NULL,
|
|
649
|
+
chunk_overlap INTEGER NOT NULL,
|
|
650
|
+
is_default BOOLEAN NOT NULL DEFAULT false
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
CREATE INDEX IF NOT EXISTS idx_embedding_config_default ON embedding_config(is_default);
|
|
654
|
+
`
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
// src/migrations/0013_templates.ts
|
|
658
|
+
var migration0013 = {
|
|
659
|
+
version: 13,
|
|
660
|
+
name: "0013_templates",
|
|
661
|
+
sql: `
|
|
662
|
+
CREATE TABLE IF NOT EXISTS template (
|
|
663
|
+
id TEXT PRIMARY KEY,
|
|
664
|
+
name TEXT NOT NULL,
|
|
665
|
+
description TEXT,
|
|
666
|
+
content TEXT NOT NULL,
|
|
667
|
+
format TEXT NOT NULL DEFAULT 'markdown',
|
|
668
|
+
default_tags JSONB NOT NULL DEFAULT '[]',
|
|
669
|
+
collection_id TEXT,
|
|
670
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
671
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
672
|
+
);
|
|
673
|
+
|
|
674
|
+
CREATE INDEX IF NOT EXISTS idx_template_collection ON template(collection_id);
|
|
675
|
+
`
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
// src/migrations/0014_url_links.ts
|
|
679
|
+
var migration0014 = {
|
|
680
|
+
version: 14,
|
|
681
|
+
name: "0014_url_links",
|
|
682
|
+
sql: `
|
|
683
|
+
CREATE TABLE IF NOT EXISTS link_url_target (
|
|
684
|
+
id TEXT PRIMARY KEY,
|
|
685
|
+
source_note_id TEXT NOT NULL,
|
|
686
|
+
to_url TEXT NOT NULL,
|
|
687
|
+
link_type TEXT NOT NULL DEFAULT 'reference',
|
|
688
|
+
confidence REAL,
|
|
689
|
+
metadata_json JSONB,
|
|
690
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
691
|
+
updated_at TIMESTAMPTZ,
|
|
692
|
+
deleted_at TIMESTAMPTZ
|
|
693
|
+
);
|
|
694
|
+
|
|
695
|
+
CREATE INDEX IF NOT EXISTS idx_link_url_source ON link_url_target(source_note_id);
|
|
696
|
+
`
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
// src/migrations/0015_embedding_set_server_metadata.ts
|
|
700
|
+
var migration0015 = {
|
|
701
|
+
version: 15,
|
|
702
|
+
name: "0015_embedding_set_server_metadata",
|
|
703
|
+
sql: `
|
|
704
|
+
ALTER TABLE embedding_set
|
|
705
|
+
ADD COLUMN IF NOT EXISTS slug TEXT;
|
|
706
|
+
|
|
707
|
+
ALTER TABLE embedding_set
|
|
708
|
+
ADD COLUMN IF NOT EXISTS description TEXT;
|
|
709
|
+
|
|
710
|
+
ALTER TABLE embedding_set
|
|
711
|
+
ADD COLUMN IF NOT EXISTS document_count INTEGER;
|
|
712
|
+
|
|
713
|
+
ALTER TABLE embedding_set
|
|
714
|
+
ADD COLUMN IF NOT EXISTS embedding_count INTEGER;
|
|
715
|
+
|
|
716
|
+
ALTER TABLE embedding_set
|
|
717
|
+
ADD COLUMN IF NOT EXISTS is_system BOOLEAN NOT NULL DEFAULT FALSE;
|
|
718
|
+
|
|
719
|
+
ALTER TABLE embedding_set
|
|
720
|
+
ADD COLUMN IF NOT EXISTS keywords_json JSONB;
|
|
721
|
+
`
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
// src/migrations/0016_embedding_server_metadata.ts
|
|
725
|
+
var migration0016 = {
|
|
726
|
+
version: 16,
|
|
727
|
+
name: "0016_embedding_server_metadata",
|
|
728
|
+
sql: `
|
|
729
|
+
ALTER TABLE embedding
|
|
730
|
+
ADD COLUMN IF NOT EXISTS chunk_index INTEGER NOT NULL DEFAULT 0;
|
|
731
|
+
|
|
732
|
+
ALTER TABLE embedding
|
|
733
|
+
ADD COLUMN IF NOT EXISTS text TEXT NOT NULL DEFAULT '';
|
|
734
|
+
|
|
735
|
+
ALTER TABLE embedding
|
|
736
|
+
ADD COLUMN IF NOT EXISTS model TEXT;
|
|
737
|
+
|
|
738
|
+
ALTER TABLE embedding
|
|
739
|
+
DROP CONSTRAINT IF EXISTS embedding_note_id_embedding_set_id_key;
|
|
740
|
+
|
|
741
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_embedding_note_set_chunk
|
|
742
|
+
ON embedding(note_id, embedding_set_id, chunk_index);
|
|
743
|
+
`
|
|
744
|
+
};
|
|
745
|
+
|
|
612
746
|
// src/migrations/index.ts
|
|
613
747
|
var allMigrations = [
|
|
614
748
|
migration0001,
|
|
@@ -620,7 +754,13 @@ var allMigrations = [
|
|
|
620
754
|
migration0007,
|
|
621
755
|
migration0008,
|
|
622
756
|
migration0009,
|
|
623
|
-
migration0010
|
|
757
|
+
migration0010,
|
|
758
|
+
migration0011,
|
|
759
|
+
migration0012,
|
|
760
|
+
migration0013,
|
|
761
|
+
migration0014,
|
|
762
|
+
migration0015,
|
|
763
|
+
migration0016
|
|
624
764
|
];
|
|
625
765
|
|
|
626
766
|
// src/data-archive.ts
|
|
@@ -730,6 +870,9 @@ function computeHash(data) {
|
|
|
730
870
|
const digest = sha256(data);
|
|
731
871
|
return `sha256:${bytesToHex(digest)}`;
|
|
732
872
|
}
|
|
873
|
+
function computeBlobHash(data) {
|
|
874
|
+
return `blake3:${bytesToHex(blake3(data))}`;
|
|
875
|
+
}
|
|
733
876
|
|
|
734
877
|
// src/repositories/notes-repository.ts
|
|
735
878
|
var NotesRepository = class {
|
|
@@ -1288,11 +1431,11 @@ var EmbeddingSetsRepository = class {
|
|
|
1288
1431
|
[input.note_id, input.embedding_set_id]
|
|
1289
1432
|
);
|
|
1290
1433
|
const embeddingId = input.id ?? generateId();
|
|
1291
|
-
const
|
|
1434
|
+
const vector = `[${input.vector.join(",")}]`;
|
|
1292
1435
|
await this.db.query(
|
|
1293
1436
|
`INSERT INTO embedding (id, note_id, embedding_set_id, vector)
|
|
1294
1437
|
VALUES ($1, $2, $3, $4::vector)`,
|
|
1295
|
-
[embeddingId, input.note_id, input.embedding_set_id,
|
|
1438
|
+
[embeddingId, input.note_id, input.embedding_set_id, vector]
|
|
1296
1439
|
);
|
|
1297
1440
|
await this.db.query(
|
|
1298
1441
|
`INSERT INTO embedding_set_member (embedding_set_id, note_id, embedding_id)
|
|
@@ -1454,7 +1597,7 @@ var EmbeddingSetsRepository = class {
|
|
|
1454
1597
|
if (criteria.conceptIds && criteria.conceptIds.length > 0) {
|
|
1455
1598
|
throw new Error("Unsupported virtual embedding-set criteria field: conceptIds");
|
|
1456
1599
|
}
|
|
1457
|
-
const conditions = ["e.embedding_set_id = $1"];
|
|
1600
|
+
const conditions = ["e.embedding_set_id = $1", "n.deleted_at IS NULL"];
|
|
1458
1601
|
const params = [source.baseSetId];
|
|
1459
1602
|
let idx = 2;
|
|
1460
1603
|
if (criteria.noteIds?.length) {
|
|
@@ -1657,7 +1800,7 @@ var ATTACHMENT_TEXT_JOIN2 = `
|
|
|
1657
1800
|
WHERE deleted_at IS NULL
|
|
1658
1801
|
GROUP BY note_id
|
|
1659
1802
|
) ax ON ax.note_id = n.id`;
|
|
1660
|
-
var COMBINED_TEXT_SQL = `(coalesce(c.content, '') || ' ' || coalesce(ax.extracted_text, ''))`;
|
|
1803
|
+
var COMBINED_TEXT_SQL = `trim(both from (coalesce(c.content, '') || ' ' || coalesce(ax.extracted_text, '')))`;
|
|
1661
1804
|
var COMBINED_TEXT_VECTOR_SQL2 = `to_tsvector('english', ${COMBINED_TEXT_SQL})`;
|
|
1662
1805
|
var SearchRepository = class {
|
|
1663
1806
|
constructor(db, semanticAvailable = false) {
|
|
@@ -2285,11 +2428,25 @@ function linkToBackend(link) {
|
|
|
2285
2428
|
createdAt: toIso(link.created_at)
|
|
2286
2429
|
};
|
|
2287
2430
|
}
|
|
2431
|
+
function urlLinkToBackend(link) {
|
|
2432
|
+
const metadata = typeof link.metadata_json === "string" ? JSON.parse(link.metadata_json) : link.metadata_json ?? void 0;
|
|
2433
|
+
return {
|
|
2434
|
+
id: link.id,
|
|
2435
|
+
fromNoteId: link.source_note_id,
|
|
2436
|
+
toNoteId: null,
|
|
2437
|
+
toUrl: link.to_url,
|
|
2438
|
+
kind: link.link_type,
|
|
2439
|
+
score: link.confidence,
|
|
2440
|
+
createdAt: toIso(link.created_at),
|
|
2441
|
+
...metadata ? { metadata } : {}
|
|
2442
|
+
};
|
|
2443
|
+
}
|
|
2288
2444
|
function shardLinkToBackend(link) {
|
|
2289
2445
|
return {
|
|
2290
2446
|
id: link.id,
|
|
2291
2447
|
fromNoteId: link.from_note_id,
|
|
2292
2448
|
toNoteId: link.to_note_id,
|
|
2449
|
+
toUrl: link.to_url,
|
|
2293
2450
|
kind: link.kind,
|
|
2294
2451
|
score: link.score,
|
|
2295
2452
|
createdAt: link.created_at,
|
|
@@ -2335,7 +2492,8 @@ function remoteLinkToBackend(link) {
|
|
|
2335
2492
|
return {
|
|
2336
2493
|
id: link.id,
|
|
2337
2494
|
fromNoteId: link.fromNoteId ?? link.from_note_id ?? link.source_note_id ?? "",
|
|
2338
|
-
toNoteId: link.toNoteId ?? link.to_note_id ?? link.target_note_id ??
|
|
2495
|
+
toNoteId: link.toNoteId ?? link.to_note_id ?? link.target_note_id ?? null,
|
|
2496
|
+
toUrl: link.toUrl ?? link.to_url ?? null,
|
|
2339
2497
|
kind: link.kind ?? link.link_type ?? "",
|
|
2340
2498
|
score: link.score ?? link.confidence ?? null,
|
|
2341
2499
|
createdAt: link.createdAt ?? link.created_at ?? "",
|
|
@@ -2357,7 +2515,15 @@ function createPGliteBackend(db, options = {}) {
|
|
|
2357
2515
|
const links = new LinksRepository(db);
|
|
2358
2516
|
async function linksOf(id) {
|
|
2359
2517
|
const result = await links.listForNote(id);
|
|
2360
|
-
|
|
2518
|
+
const urlLinks = await db.query(
|
|
2519
|
+
`SELECT * FROM link_url_target WHERE source_note_id = $1 AND deleted_at IS NULL ORDER BY created_at`,
|
|
2520
|
+
[id]
|
|
2521
|
+
);
|
|
2522
|
+
return [
|
|
2523
|
+
...result.outbound.map(linkToBackend),
|
|
2524
|
+
...result.inbound.map(linkToBackend),
|
|
2525
|
+
...urlLinks.rows.map(urlLinkToBackend)
|
|
2526
|
+
];
|
|
2361
2527
|
}
|
|
2362
2528
|
async function conceptsOf(id) {
|
|
2363
2529
|
const result = await db.query(
|
|
@@ -2988,6 +3154,20 @@ var MigrationRunner = class {
|
|
|
2988
3154
|
|
|
2989
3155
|
// src/archive-manager.ts
|
|
2990
3156
|
var ArchiveManager = class {
|
|
3157
|
+
/**
|
|
3158
|
+
* Persistence is a PLUGGABLE, opt-in backend (issue #261). Pass a
|
|
3159
|
+
* `StorageBackendFactory` to run against any `StorageBackend`
|
|
3160
|
+
* implementation; PGlite is merely the built-in default, selected only when
|
|
3161
|
+
* the convenience `PersistenceMode` string form is used. Because
|
|
3162
|
+
* `defaultStorageBackendFactory` reaches PGlite through the lazily-imported
|
|
3163
|
+
* `createPGliteInstance` (see `db.ts`), a consumer that supplies its own
|
|
3164
|
+
* factory — or never opens an archive at all — never pulls the PGlite WASM
|
|
3165
|
+
* engine into its bundle.
|
|
3166
|
+
*
|
|
3167
|
+
* @param persistenceOrFactory `PersistenceMode` string (uses the built-in
|
|
3168
|
+
* PGlite backend) OR a custom `StorageBackendFactory`.
|
|
3169
|
+
* @param persistenceOverride persistence hint passed to a custom factory.
|
|
3170
|
+
*/
|
|
2991
3171
|
constructor(persistenceOrFactory, events, persistenceOverride) {
|
|
2992
3172
|
this.events = events;
|
|
2993
3173
|
if (typeof persistenceOrFactory === "string") {
|
|
@@ -3096,6 +3276,205 @@ function createFortemi(config) {
|
|
|
3096
3276
|
};
|
|
3097
3277
|
}
|
|
3098
3278
|
|
|
3279
|
+
// src/server-compatibility.ts
|
|
3280
|
+
var FORTEMI_COMPATIBILITY_PATH = "/api/v1/system/compatibility";
|
|
3281
|
+
var FORTEMI_COMPATIBILITY_STATES = [
|
|
3282
|
+
"available",
|
|
3283
|
+
"degraded",
|
|
3284
|
+
"preview",
|
|
3285
|
+
"unavailable",
|
|
3286
|
+
"unknown"
|
|
3287
|
+
];
|
|
3288
|
+
var FORTEMI_REQUIRED_COMPATIBILITY_CAPABILITIES = [
|
|
3289
|
+
"core_notes",
|
|
3290
|
+
"search",
|
|
3291
|
+
"jobs",
|
|
3292
|
+
"realtime_activity",
|
|
3293
|
+
"hosted_auth",
|
|
3294
|
+
"premium_components",
|
|
3295
|
+
"backoffice_api",
|
|
3296
|
+
"audit_posture",
|
|
3297
|
+
"quota_status",
|
|
3298
|
+
"kms_status",
|
|
3299
|
+
"mcp_scope_gate"
|
|
3300
|
+
];
|
|
3301
|
+
function isRecord(value) {
|
|
3302
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
3303
|
+
}
|
|
3304
|
+
function isSupportedState(value) {
|
|
3305
|
+
return typeof value === "string" && FORTEMI_COMPATIBILITY_STATES.includes(value);
|
|
3306
|
+
}
|
|
3307
|
+
function requireRecord(parent, key, errors) {
|
|
3308
|
+
const value = parent[key];
|
|
3309
|
+
if (!isRecord(value)) {
|
|
3310
|
+
errors.push(`${key} must be an object`);
|
|
3311
|
+
return null;
|
|
3312
|
+
}
|
|
3313
|
+
return value;
|
|
3314
|
+
}
|
|
3315
|
+
function requireString(parent, key, path, errors) {
|
|
3316
|
+
const value = parent[key];
|
|
3317
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
3318
|
+
errors.push(`${path}.${key} must be a non-empty string`);
|
|
3319
|
+
return null;
|
|
3320
|
+
}
|
|
3321
|
+
return value;
|
|
3322
|
+
}
|
|
3323
|
+
function requireBoolean(parent, key, path, errors) {
|
|
3324
|
+
const value = parent[key];
|
|
3325
|
+
if (typeof value !== "boolean") {
|
|
3326
|
+
errors.push(`${path}.${key} must be a boolean`);
|
|
3327
|
+
return null;
|
|
3328
|
+
}
|
|
3329
|
+
return value;
|
|
3330
|
+
}
|
|
3331
|
+
function fortemiCompatibilityUrl(baseUrl = "http://localhost:3000") {
|
|
3332
|
+
const trimmed = baseUrl.trim().replace(/\/+$/, "");
|
|
3333
|
+
if (trimmed.endsWith(FORTEMI_COMPATIBILITY_PATH)) return trimmed;
|
|
3334
|
+
return `${trimmed}${FORTEMI_COMPATIBILITY_PATH}`;
|
|
3335
|
+
}
|
|
3336
|
+
function validateFortemiCompatibilityResponse(raw) {
|
|
3337
|
+
const errors = [];
|
|
3338
|
+
const warnings = [];
|
|
3339
|
+
if (!isRecord(raw)) {
|
|
3340
|
+
return {
|
|
3341
|
+
ok: false,
|
|
3342
|
+
errors: ["response must be a JSON object"],
|
|
3343
|
+
warnings
|
|
3344
|
+
};
|
|
3345
|
+
}
|
|
3346
|
+
if (raw.schema_version !== 1) {
|
|
3347
|
+
errors.push("schema_version must be 1");
|
|
3348
|
+
}
|
|
3349
|
+
requireString(raw, "contract_revision", "response", errors);
|
|
3350
|
+
const api = requireRecord(raw, "api", errors);
|
|
3351
|
+
if (api) {
|
|
3352
|
+
const name = requireString(api, "name", "api", errors);
|
|
3353
|
+
if (name && name !== "fortemi") {
|
|
3354
|
+
errors.push("api.name must be fortemi");
|
|
3355
|
+
}
|
|
3356
|
+
requireString(api, "version", "api", errors);
|
|
3357
|
+
requireString(api, "minimum_hotm_enterprise_client", "api", errors);
|
|
3358
|
+
requireBoolean(api, "git_sha_present", "api", errors);
|
|
3359
|
+
requireBoolean(api, "build_date_present", "api", errors);
|
|
3360
|
+
}
|
|
3361
|
+
const deployment = requireRecord(raw, "deployment", errors);
|
|
3362
|
+
if (deployment) {
|
|
3363
|
+
requireString(deployment, "mode", "deployment", errors);
|
|
3364
|
+
requireString(deployment, "edition", "deployment", errors);
|
|
3365
|
+
requireBoolean(deployment, "hosted_multi_tenant_ready", "deployment", errors);
|
|
3366
|
+
}
|
|
3367
|
+
const auth = requireRecord(raw, "auth", errors);
|
|
3368
|
+
if (auth) {
|
|
3369
|
+
requireBoolean(auth, "required", "auth", errors);
|
|
3370
|
+
requireString(auth, "mode", "auth", errors);
|
|
3371
|
+
requireBoolean(auth, "oauth_issuer_configured", "auth", errors);
|
|
3372
|
+
requireBoolean(auth, "tenant_context_available", "auth", errors);
|
|
3373
|
+
}
|
|
3374
|
+
const capabilities = requireRecord(raw, "capabilities", errors);
|
|
3375
|
+
if (capabilities) {
|
|
3376
|
+
for (const key of FORTEMI_REQUIRED_COMPATIBILITY_CAPABILITIES) {
|
|
3377
|
+
if (!isRecord(capabilities[key])) {
|
|
3378
|
+
errors.push(`capabilities.${key} must be present as an object`);
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
for (const [key, value] of Object.entries(capabilities)) {
|
|
3382
|
+
if (!isRecord(value)) {
|
|
3383
|
+
errors.push(`capabilities.${key} must be an object`);
|
|
3384
|
+
continue;
|
|
3385
|
+
}
|
|
3386
|
+
if (!isSupportedState(value.state)) {
|
|
3387
|
+
errors.push(`capabilities.${key}.state must be one of ${FORTEMI_COMPATIBILITY_STATES.join(", ")}`);
|
|
3388
|
+
}
|
|
3389
|
+
if ("reason_code" in value && typeof value.reason_code !== "string") {
|
|
3390
|
+
errors.push(`capabilities.${key}.reason_code must be a string when present`);
|
|
3391
|
+
}
|
|
3392
|
+
if (value.state !== "available" && !value.reason_code) {
|
|
3393
|
+
warnings.push(`capabilities.${key} is ${String(value.state)} without reason_code`);
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
const links = requireRecord(raw, "links", errors);
|
|
3398
|
+
if (links) {
|
|
3399
|
+
requireString(links, "openapi", "links", errors);
|
|
3400
|
+
requireString(links, "asyncapi", "links", errors);
|
|
3401
|
+
requireString(links, "health", "links", errors);
|
|
3402
|
+
requireString(links, "streaming_health", "links", errors);
|
|
3403
|
+
}
|
|
3404
|
+
return {
|
|
3405
|
+
ok: errors.length === 0,
|
|
3406
|
+
errors,
|
|
3407
|
+
warnings,
|
|
3408
|
+
response: errors.length === 0 ? raw : void 0
|
|
3409
|
+
};
|
|
3410
|
+
}
|
|
3411
|
+
function formatFortemiCompatibilitySummary(response) {
|
|
3412
|
+
const capabilitySummary = Object.entries(response.capabilities).map(([key, value]) => `${key}=${value.state}`).sort().join(", ");
|
|
3413
|
+
return [
|
|
3414
|
+
`Fortemi compatibility ${response.contract_revision}`,
|
|
3415
|
+
`api=${response.api.name}@${response.api.version}`,
|
|
3416
|
+
`deployment=${response.deployment.mode}/${response.deployment.edition}`,
|
|
3417
|
+
`auth=${response.auth.mode}`,
|
|
3418
|
+
`capabilities: ${capabilitySummary}`
|
|
3419
|
+
].join("\n");
|
|
3420
|
+
}
|
|
3421
|
+
async function fetchAndValidateFortemiCompatibility(options = {}) {
|
|
3422
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
3423
|
+
if (!fetchImpl) {
|
|
3424
|
+
return {
|
|
3425
|
+
ok: false,
|
|
3426
|
+
url: fortemiCompatibilityUrl(options.baseUrl),
|
|
3427
|
+
errors: ["fetch is not available in this runtime"],
|
|
3428
|
+
warnings: []
|
|
3429
|
+
};
|
|
3430
|
+
}
|
|
3431
|
+
const url = fortemiCompatibilityUrl(options.baseUrl);
|
|
3432
|
+
const controller = new AbortController();
|
|
3433
|
+
const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 5e3);
|
|
3434
|
+
try {
|
|
3435
|
+
const response = await fetchImpl(url, {
|
|
3436
|
+
method: "GET",
|
|
3437
|
+
headers: { accept: "application/json" },
|
|
3438
|
+
signal: controller.signal
|
|
3439
|
+
});
|
|
3440
|
+
const status = response.status;
|
|
3441
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
3442
|
+
if (!response.ok) {
|
|
3443
|
+
return {
|
|
3444
|
+
ok: false,
|
|
3445
|
+
url,
|
|
3446
|
+
status,
|
|
3447
|
+
errors: [`Fortemi compatibility endpoint returned HTTP ${status}`],
|
|
3448
|
+
warnings: []
|
|
3449
|
+
};
|
|
3450
|
+
}
|
|
3451
|
+
if (!contentType.toLowerCase().includes("application/json")) {
|
|
3452
|
+
return {
|
|
3453
|
+
ok: false,
|
|
3454
|
+
url,
|
|
3455
|
+
status,
|
|
3456
|
+
errors: [`Fortemi compatibility endpoint must return JSON; got ${contentType || "unknown content-type"}`],
|
|
3457
|
+
warnings: []
|
|
3458
|
+
};
|
|
3459
|
+
}
|
|
3460
|
+
return {
|
|
3461
|
+
...validateFortemiCompatibilityResponse(await response.json()),
|
|
3462
|
+
url,
|
|
3463
|
+
status
|
|
3464
|
+
};
|
|
3465
|
+
} catch (error) {
|
|
3466
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3467
|
+
return {
|
|
3468
|
+
ok: false,
|
|
3469
|
+
url,
|
|
3470
|
+
errors: [`Fortemi compatibility endpoint is unreachable: ${message}`],
|
|
3471
|
+
warnings: []
|
|
3472
|
+
};
|
|
3473
|
+
} finally {
|
|
3474
|
+
clearTimeout(timeout);
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
|
|
3099
3478
|
// src/service-worker/register.ts
|
|
3100
3479
|
async function registerServiceWorker(swUrl = "/sw.js") {
|
|
3101
3480
|
if (!("serviceWorker" in navigator)) {
|
|
@@ -3862,6 +4241,7 @@ async function getNoteTextWithExtractedAttachments(db, noteId) {
|
|
|
3862
4241
|
' ORDER BY a.position, a.created_at)
|
|
3863
4242
|
FILTER (WHERE a.extracted_text IS NOT NULL AND a.extracted_text <> ''), '') as extracted_text
|
|
3864
4243
|
FROM note_revised_current c
|
|
4244
|
+
JOIN note n ON n.id = c.note_id AND n.deleted_at IS NULL
|
|
3865
4245
|
LEFT JOIN attachment a ON a.note_id = c.note_id AND a.deleted_at IS NULL
|
|
3866
4246
|
WHERE c.note_id = $1
|
|
3867
4247
|
GROUP BY c.content`,
|
|
@@ -4159,7 +4539,7 @@ function conceptTaggingHandler(job, db) {
|
|
|
4159
4539
|
const llmFn2 = getLlmFunction();
|
|
4160
4540
|
if (!llmFn2) return { skipped: true, reason: "no LLM function registered" };
|
|
4161
4541
|
const noteText = await getNoteTextWithExtractedAttachments(db, job.note_id);
|
|
4162
|
-
if (!noteText)
|
|
4542
|
+
if (!noteText) return { skipped: true, reason: "note missing, deleted, or has no content" };
|
|
4163
4543
|
const content = noteText.combined;
|
|
4164
4544
|
const prompt = `Task: Extract 3-5 topic tags from the text below.
|
|
4165
4545
|
Rules:
|
|
@@ -4197,20 +4577,25 @@ Tags:`;
|
|
|
4197
4577
|
function linkingHandler(job, db) {
|
|
4198
4578
|
return (async () => {
|
|
4199
4579
|
const embResult = await db.query(
|
|
4200
|
-
`SELECT id FROM embedding
|
|
4580
|
+
`SELECT e.id FROM embedding e
|
|
4581
|
+
JOIN note n ON n.id = e.note_id AND n.deleted_at IS NULL
|
|
4582
|
+
WHERE e.note_id = $1 LIMIT 1`,
|
|
4201
4583
|
[job.note_id]
|
|
4202
4584
|
);
|
|
4203
4585
|
if (embResult.rows.length === 0) {
|
|
4204
4586
|
return { skipped: true, reason: "no embeddings for this note yet" };
|
|
4205
4587
|
}
|
|
4206
4588
|
const vecResult = await db.query(
|
|
4207
|
-
`SELECT vector::text FROM embedding
|
|
4589
|
+
`SELECT e.vector::text FROM embedding e
|
|
4590
|
+
JOIN note n ON n.id = e.note_id AND n.deleted_at IS NULL
|
|
4591
|
+
WHERE e.note_id = $1 LIMIT 1`,
|
|
4208
4592
|
[job.note_id]
|
|
4209
4593
|
);
|
|
4210
4594
|
if (vecResult.rows.length === 0) return { skipped: true, reason: "no vector found" };
|
|
4211
4595
|
const similar = await db.query(
|
|
4212
4596
|
`SELECT e.note_id, e.vector <=> (SELECT vector FROM embedding WHERE note_id = $1 LIMIT 1) as distance
|
|
4213
4597
|
FROM embedding e
|
|
4598
|
+
JOIN note n ON n.id = e.note_id AND n.deleted_at IS NULL
|
|
4214
4599
|
WHERE e.note_id != $1
|
|
4215
4600
|
ORDER BY distance ASC
|
|
4216
4601
|
LIMIT 5`,
|
|
@@ -5027,14 +5412,16 @@ var AttachmentsRepository = class {
|
|
|
5027
5412
|
/**
|
|
5028
5413
|
* Attach a binary file to a note.
|
|
5029
5414
|
*
|
|
5030
|
-
* If a blob with the same
|
|
5415
|
+
* If a blob with the same BLAKE3 content hash already exists, the existing
|
|
5031
5416
|
* blob row is reused (deduplication). Otherwise a new blob row is inserted
|
|
5032
|
-
* and the raw bytes are written to the BlobStore.
|
|
5417
|
+
* and the raw bytes are written to the BlobStore. The BLAKE3 `content_hash`
|
|
5418
|
+
* (`blake3:<hex>`) matches the server convention and is the key used by the
|
|
5419
|
+
* portable Knowledge-Shard byte sidecar.
|
|
5033
5420
|
*
|
|
5034
5421
|
* Returns the newly created AttachmentRow.
|
|
5035
5422
|
*/
|
|
5036
5423
|
async attach(input) {
|
|
5037
|
-
const contentHash =
|
|
5424
|
+
const contentHash = computeBlobHash(input.data);
|
|
5038
5425
|
const sizeBytes = input.data.length;
|
|
5039
5426
|
let blobId;
|
|
5040
5427
|
const existing = await this.db.query(
|
|
@@ -5148,6 +5535,10 @@ async function manageAttachments(db, blobStore, rawInput) {
|
|
|
5148
5535
|
extractedText: input.extracted_text,
|
|
5149
5536
|
displayName: input.display_name
|
|
5150
5537
|
});
|
|
5538
|
+
if (input.extracted_text) {
|
|
5539
|
+
await enqueueJob(db, { noteId: input.note_id, jobType: "embedding" });
|
|
5540
|
+
await enqueueJob(db, { noteId: input.note_id, jobType: "concept_tagging" });
|
|
5541
|
+
}
|
|
5151
5542
|
return { action: "attach", attachment, size_bytes: data.length };
|
|
5152
5543
|
}
|
|
5153
5544
|
case "list": {
|
|
@@ -5171,7 +5562,12 @@ async function manageAttachments(db, blobStore, rawInput) {
|
|
|
5171
5562
|
}
|
|
5172
5563
|
case "delete": {
|
|
5173
5564
|
if (!input.attachment_id) throw new Error("attachment_id required for delete");
|
|
5565
|
+
const attachment = await repo.get(input.attachment_id);
|
|
5174
5566
|
await repo.delete(input.attachment_id);
|
|
5567
|
+
if (attachment.extracted_text) {
|
|
5568
|
+
await enqueueJob(db, { noteId: attachment.note_id, jobType: "embedding" });
|
|
5569
|
+
await enqueueJob(db, { noteId: attachment.note_id, jobType: "concept_tagging" });
|
|
5570
|
+
}
|
|
5175
5571
|
return { action: "delete", attachment_id: input.attachment_id };
|
|
5176
5572
|
}
|
|
5177
5573
|
}
|
|
@@ -5398,17 +5794,17 @@ async function embeddingGenerationHandler(job, db) {
|
|
|
5398
5794
|
const fn = embedFn;
|
|
5399
5795
|
if (!fn) return { skipped: true, reason: "no embed function registered" };
|
|
5400
5796
|
const noteText = await getNoteTextWithExtractedAttachments(db, job.note_id);
|
|
5401
|
-
if (!noteText)
|
|
5797
|
+
if (!noteText) return { skipped: true, reason: "note missing, deleted, or has no content" };
|
|
5402
5798
|
const content = noteText.combined;
|
|
5403
5799
|
const chunks = chunkText(content);
|
|
5404
5800
|
const embeddings = await fn(chunks);
|
|
5405
|
-
const
|
|
5801
|
+
const vector = averageEmbeddings(embeddings);
|
|
5406
5802
|
const embeddingSets = new EmbeddingSetsRepository(db);
|
|
5407
5803
|
const set = await embeddingSets.ensureDefault();
|
|
5408
5804
|
await embeddingSets.putEmbedding({
|
|
5409
5805
|
note_id: job.note_id,
|
|
5410
5806
|
embedding_set_id: set.id,
|
|
5411
|
-
vector
|
|
5807
|
+
vector
|
|
5412
5808
|
});
|
|
5413
5809
|
return { chunks: chunks.length, embeddings: embeddings.length, setId: set.id };
|
|
5414
5810
|
}
|
|
@@ -5827,13 +6223,13 @@ var OpenAICompatibleProvider = class {
|
|
|
5827
6223
|
throw new Error("Streaming not supported: response body is null");
|
|
5828
6224
|
}
|
|
5829
6225
|
const reader = response.body.getReader();
|
|
5830
|
-
const
|
|
6226
|
+
const decoder6 = new TextDecoder();
|
|
5831
6227
|
let buffer = "";
|
|
5832
6228
|
try {
|
|
5833
6229
|
while (true) {
|
|
5834
6230
|
const { done, value } = await reader.read();
|
|
5835
6231
|
if (done) break;
|
|
5836
|
-
buffer +=
|
|
6232
|
+
buffer += decoder6.decode(value, { stream: true });
|
|
5837
6233
|
const lines = buffer.split("\n");
|
|
5838
6234
|
buffer = lines.pop() ?? "";
|
|
5839
6235
|
for (const line of lines) {
|
|
@@ -6453,6 +6849,23 @@ function createCspReportHandler(onReport) {
|
|
|
6453
6849
|
// src/shard/types.ts
|
|
6454
6850
|
var CURRENT_SHARD_VERSION = "1.0.0";
|
|
6455
6851
|
var SHARD_FORMAT = "matric-shard";
|
|
6852
|
+
function parseVersion(value) {
|
|
6853
|
+
return value.split(".").map((segment) => {
|
|
6854
|
+
const match = segment.match(/^\d+/);
|
|
6855
|
+
return match ? Number.parseInt(match[0], 10) : 0;
|
|
6856
|
+
});
|
|
6857
|
+
}
|
|
6858
|
+
function compareShardVersions(left, right) {
|
|
6859
|
+
const leftParts = parseVersion(left);
|
|
6860
|
+
const rightParts = parseVersion(right);
|
|
6861
|
+
const length = Math.max(leftParts.length, rightParts.length);
|
|
6862
|
+
for (let index = 0; index < length; index += 1) {
|
|
6863
|
+
const leftValue = leftParts[index] ?? 0;
|
|
6864
|
+
const rightValue = rightParts[index] ?? 0;
|
|
6865
|
+
if (leftValue !== rightValue) return leftValue > rightValue ? 1 : -1;
|
|
6866
|
+
}
|
|
6867
|
+
return 0;
|
|
6868
|
+
}
|
|
6456
6869
|
var BLOCK_SIZE = 512;
|
|
6457
6870
|
var USTAR_MAGIC = "ustar\x0000";
|
|
6458
6871
|
function writeString(buf, offset, str, len) {
|
|
@@ -6542,12 +6955,35 @@ function decodeTar(tarData) {
|
|
|
6542
6955
|
}
|
|
6543
6956
|
return files;
|
|
6544
6957
|
}
|
|
6545
|
-
function packTarGz(files) {
|
|
6958
|
+
function packTarGz(files, opts) {
|
|
6546
6959
|
const tarData = encodeTar(files);
|
|
6960
|
+
const cap = opts?.maxDecompressedBytes ?? DEFAULT_MAX_DECOMPRESSED_BYTES;
|
|
6961
|
+
if (tarData.byteLength > cap) {
|
|
6962
|
+
throw new Error(
|
|
6963
|
+
"Refusing to create archive: uncompressed size " + tarData.byteLength + " exceeds cap " + cap + " bytes"
|
|
6964
|
+
);
|
|
6965
|
+
}
|
|
6547
6966
|
return gzipSync(tarData);
|
|
6548
6967
|
}
|
|
6549
|
-
|
|
6968
|
+
var DEFAULT_MAX_DECOMPRESSED_BYTES = 256 * 1024 * 1024;
|
|
6969
|
+
function unpackTarGz(data, opts) {
|
|
6970
|
+
const cap = opts?.maxDecompressedBytes ?? DEFAULT_MAX_DECOMPRESSED_BYTES;
|
|
6971
|
+
if (data.byteLength < 18) {
|
|
6972
|
+
throw new Error("Invalid gzip archive: too short");
|
|
6973
|
+
}
|
|
6974
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
6975
|
+
const declaredSize = view.getUint32(data.byteLength - 4, true);
|
|
6976
|
+
if (declaredSize > cap) {
|
|
6977
|
+
throw new Error(
|
|
6978
|
+
"Refusing to decompress archive: declared size " + declaredSize + " exceeds cap " + cap + " bytes"
|
|
6979
|
+
);
|
|
6980
|
+
}
|
|
6550
6981
|
const tarData = gunzipSync(data);
|
|
6982
|
+
if (tarData.byteLength > cap) {
|
|
6983
|
+
throw new Error(
|
|
6984
|
+
"Refusing to decompress archive: decompressed size " + tarData.byteLength + " exceeds cap " + cap + " bytes"
|
|
6985
|
+
);
|
|
6986
|
+
}
|
|
6551
6987
|
return decodeTar(tarData);
|
|
6552
6988
|
}
|
|
6553
6989
|
|
|
@@ -6582,7 +7018,8 @@ function noteToShard(note) {
|
|
|
6582
7018
|
title: note.title,
|
|
6583
7019
|
original_content: note.original_content,
|
|
6584
7020
|
revised_content: note.revised_content,
|
|
6585
|
-
|
|
7021
|
+
collection_id: note.collection_id ?? null,
|
|
7022
|
+
...note.attachments?.length ? { attachments: note.attachments } : {},
|
|
6586
7023
|
format: note.format,
|
|
6587
7024
|
source: note.source,
|
|
6588
7025
|
starred: note.is_starred,
|
|
@@ -6594,6 +7031,7 @@ function noteToShard(note) {
|
|
|
6594
7031
|
};
|
|
6595
7032
|
}
|
|
6596
7033
|
function noteFromShard(shard) {
|
|
7034
|
+
const attachments = shard.attachments ?? shard.binary_sources;
|
|
6597
7035
|
return {
|
|
6598
7036
|
id: shard.id,
|
|
6599
7037
|
title: shard.title,
|
|
@@ -6603,7 +7041,8 @@ function noteFromShard(shard) {
|
|
|
6603
7041
|
is_archived: shard.archived,
|
|
6604
7042
|
original_content: shard.original_content,
|
|
6605
7043
|
revised_content: shard.revised_content,
|
|
6606
|
-
|
|
7044
|
+
collection_id: shard.collection_id ?? null,
|
|
7045
|
+
attachments,
|
|
6607
7046
|
tags: shard.tags,
|
|
6608
7047
|
created_at: shard.created_at,
|
|
6609
7048
|
updated_at: shard.updated_at,
|
|
@@ -6615,9 +7054,24 @@ function linkToShard(link) {
|
|
|
6615
7054
|
id: link.id,
|
|
6616
7055
|
from_note_id: link.source_note_id,
|
|
6617
7056
|
to_note_id: link.target_note_id,
|
|
7057
|
+
to_url: null,
|
|
7058
|
+
kind: link.link_type,
|
|
7059
|
+
score: link.confidence,
|
|
7060
|
+
created_at: toISOString(link.created_at),
|
|
7061
|
+
metadata: null
|
|
7062
|
+
};
|
|
7063
|
+
}
|
|
7064
|
+
function urlLinkToShard(link) {
|
|
7065
|
+
const metadata = typeof link.metadata_json === "string" ? JSON.parse(link.metadata_json) : link.metadata_json ?? null;
|
|
7066
|
+
return {
|
|
7067
|
+
id: link.id,
|
|
7068
|
+
from_note_id: link.source_note_id,
|
|
7069
|
+
to_note_id: null,
|
|
7070
|
+
to_url: link.to_url,
|
|
6618
7071
|
kind: link.link_type,
|
|
6619
7072
|
score: link.confidence,
|
|
6620
|
-
created_at: toISOString(link.created_at)
|
|
7073
|
+
created_at: toISOString(link.created_at),
|
|
7074
|
+
metadata
|
|
6621
7075
|
};
|
|
6622
7076
|
}
|
|
6623
7077
|
function linkFromShard(shard) {
|
|
@@ -6625,9 +7079,11 @@ function linkFromShard(shard) {
|
|
|
6625
7079
|
id: shard.id,
|
|
6626
7080
|
source_note_id: shard.from_note_id,
|
|
6627
7081
|
target_note_id: shard.to_note_id,
|
|
7082
|
+
to_url: shard.to_url,
|
|
6628
7083
|
link_type: shard.kind,
|
|
6629
7084
|
confidence: shard.score,
|
|
6630
|
-
created_at: shard.created_at
|
|
7085
|
+
created_at: shard.created_at,
|
|
7086
|
+
metadata: shard.metadata
|
|
6631
7087
|
};
|
|
6632
7088
|
}
|
|
6633
7089
|
function collectionToShard(collection, noteCount) {
|
|
@@ -6655,14 +7111,31 @@ function tagsToShard(allTags) {
|
|
|
6655
7111
|
created_at: toISOString(t.created_at)
|
|
6656
7112
|
}));
|
|
6657
7113
|
}
|
|
6658
|
-
function
|
|
6659
|
-
return
|
|
7114
|
+
function templateToShard(template) {
|
|
7115
|
+
return {
|
|
7116
|
+
id: template.id,
|
|
7117
|
+
name: template.name,
|
|
7118
|
+
description: template.description,
|
|
7119
|
+
content: template.content,
|
|
7120
|
+
format: template.format,
|
|
7121
|
+
default_tags: Array.isArray(template.default_tags) ? template.default_tags : JSON.parse(template.default_tags),
|
|
7122
|
+
collection_id: template.collection_id,
|
|
7123
|
+
created_at: toISOString(template.created_at),
|
|
7124
|
+
updated_at: toISOString(template.updated_at)
|
|
7125
|
+
};
|
|
6660
7126
|
}
|
|
6661
7127
|
function embeddingSetToShard(set) {
|
|
7128
|
+
const name = set.name ?? set.model_name;
|
|
6662
7129
|
return {
|
|
6663
7130
|
id: set.id,
|
|
6664
|
-
name
|
|
7131
|
+
name,
|
|
7132
|
+
slug: set.slug ?? slugifyEmbeddingSet(name),
|
|
7133
|
+
description: set.description ?? null,
|
|
6665
7134
|
purpose: set.purpose ?? null,
|
|
7135
|
+
document_count: set.document_count ?? 0,
|
|
7136
|
+
embedding_count: set.embedding_count ?? 0,
|
|
7137
|
+
is_system: set.is_system ?? false,
|
|
7138
|
+
keywords: jsonStringArray(set.keywords_json),
|
|
6666
7139
|
model: set.model_name,
|
|
6667
7140
|
dimension: set.dimensions,
|
|
6668
7141
|
kind: set.kind ?? "physical",
|
|
@@ -6677,11 +7150,17 @@ function embeddingSetToShard(set) {
|
|
|
6677
7150
|
updated_at: set.updated_at ? toISOString(set.updated_at) : void 0
|
|
6678
7151
|
};
|
|
6679
7152
|
}
|
|
6680
|
-
function embeddingSetFromShard(shard) {
|
|
7153
|
+
function embeddingSetFromShard(shard, fallbackCreatedAt) {
|
|
6681
7154
|
return {
|
|
6682
7155
|
id: shard.id,
|
|
6683
7156
|
name: shard.name ?? shard.model,
|
|
7157
|
+
slug: shard.slug ?? null,
|
|
7158
|
+
description: shard.description ?? null,
|
|
6684
7159
|
purpose: shard.purpose ?? null,
|
|
7160
|
+
document_count: shard.document_count ?? null,
|
|
7161
|
+
embedding_count: shard.embedding_count ?? null,
|
|
7162
|
+
is_system: shard.is_system ?? false,
|
|
7163
|
+
keywords_json: jsonString(shard.keywords ?? []),
|
|
6685
7164
|
model_name: shard.model,
|
|
6686
7165
|
dimensions: shard.dimension,
|
|
6687
7166
|
kind: shard.kind ?? "physical",
|
|
@@ -6692,7 +7171,7 @@ function embeddingSetFromShard(shard) {
|
|
|
6692
7171
|
compatibility_json: jsonString(shard.compatibility),
|
|
6693
7172
|
materialization_json: jsonString(shard.materialization),
|
|
6694
7173
|
freshness_json: jsonString(shard.freshness),
|
|
6695
|
-
created_at: shard.created_at,
|
|
7174
|
+
created_at: shard.created_at ?? fallbackCreatedAt,
|
|
6696
7175
|
updated_at: shard.updated_at ?? null
|
|
6697
7176
|
};
|
|
6698
7177
|
}
|
|
@@ -6700,15 +7179,32 @@ function embeddingSetMemberToShard(member) {
|
|
|
6700
7179
|
return {
|
|
6701
7180
|
embedding_set_id: member.embedding_set_id,
|
|
6702
7181
|
note_id: member.note_id,
|
|
6703
|
-
|
|
7182
|
+
membership_type: member.membership_type ?? "materialized",
|
|
7183
|
+
added_at: toISOString(member.added_at ?? /* @__PURE__ */ new Date()),
|
|
7184
|
+
added_by: member.added_by ?? null
|
|
7185
|
+
};
|
|
7186
|
+
}
|
|
7187
|
+
function embeddingConfigToShard(config) {
|
|
7188
|
+
return {
|
|
7189
|
+
id: config.id,
|
|
7190
|
+
name: config.name,
|
|
7191
|
+
description: config.description ?? null,
|
|
7192
|
+
model: config.model,
|
|
7193
|
+
dimension: config.dimension,
|
|
7194
|
+
chunk_size: config.chunk_size,
|
|
7195
|
+
chunk_overlap: config.chunk_overlap,
|
|
7196
|
+
is_default: config.is_default
|
|
6704
7197
|
};
|
|
6705
7198
|
}
|
|
6706
7199
|
function embeddingToShard(emb) {
|
|
6707
7200
|
return {
|
|
6708
7201
|
id: emb.id,
|
|
6709
7202
|
note_id: emb.note_id,
|
|
6710
|
-
|
|
7203
|
+
chunk_index: emb.chunk_index ?? 0,
|
|
7204
|
+
text: emb.text ?? "",
|
|
6711
7205
|
vector: typeof emb.vector === "string" ? parseVector2(emb.vector) : emb.vector,
|
|
7206
|
+
model: emb.model ?? emb.model_name ?? "unknown",
|
|
7207
|
+
embedding_set_id: emb.embedding_set_id,
|
|
6712
7208
|
created_at: toISOString(emb.created_at)
|
|
6713
7209
|
};
|
|
6714
7210
|
}
|
|
@@ -6716,9 +7212,12 @@ function embeddingFromShard(shard) {
|
|
|
6716
7212
|
return {
|
|
6717
7213
|
id: shard.id,
|
|
6718
7214
|
note_id: shard.note_id,
|
|
6719
|
-
embedding_set_id: shard.embedding_set_id,
|
|
7215
|
+
embedding_set_id: shard.embedding_set_id ?? null,
|
|
7216
|
+
chunk_index: shard.chunk_index,
|
|
7217
|
+
text: shard.text,
|
|
6720
7218
|
vector: `[${shard.vector.join(",")}]`,
|
|
6721
|
-
|
|
7219
|
+
model: shard.model,
|
|
7220
|
+
created_at: shard.created_at ?? null
|
|
6722
7221
|
};
|
|
6723
7222
|
}
|
|
6724
7223
|
function skosSchemeToShard(scheme) {
|
|
@@ -6790,6 +7289,15 @@ function parseJsonObjectField(value) {
|
|
|
6790
7289
|
const parsed = JSON.parse(value);
|
|
6791
7290
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
6792
7291
|
}
|
|
7292
|
+
function jsonStringArray(value) {
|
|
7293
|
+
if (value == null) return [];
|
|
7294
|
+
if (Array.isArray(value)) return value.map(String);
|
|
7295
|
+
if (typeof value === "string") {
|
|
7296
|
+
const parsed = JSON.parse(value);
|
|
7297
|
+
return Array.isArray(parsed) ? parsed.map(String) : [];
|
|
7298
|
+
}
|
|
7299
|
+
return [];
|
|
7300
|
+
}
|
|
6793
7301
|
function jsonObject2(value) {
|
|
6794
7302
|
if (value == null) return null;
|
|
6795
7303
|
if (typeof value === "string") return JSON.parse(value);
|
|
@@ -6798,6 +7306,34 @@ function jsonObject2(value) {
|
|
|
6798
7306
|
function jsonString(value) {
|
|
6799
7307
|
return value == null ? null : JSON.stringify(value);
|
|
6800
7308
|
}
|
|
7309
|
+
function slugifyEmbeddingSet(value) {
|
|
7310
|
+
const slug = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
7311
|
+
return slug || "embedding-set";
|
|
7312
|
+
}
|
|
7313
|
+
|
|
7314
|
+
// src/shard/blob-sidecar.ts
|
|
7315
|
+
var SIDECAR_PREFIX = "blobs/";
|
|
7316
|
+
function blobChecksumToHex(checksum) {
|
|
7317
|
+
const sep = checksum.indexOf(":");
|
|
7318
|
+
return sep >= 0 ? checksum.slice(sep + 1) : checksum;
|
|
7319
|
+
}
|
|
7320
|
+
function sidecarEntryName(checksum) {
|
|
7321
|
+
return SIDECAR_PREFIX + blobChecksumToHex(checksum);
|
|
7322
|
+
}
|
|
7323
|
+
function isSidecarEntry(name) {
|
|
7324
|
+
if (!name.startsWith(SIDECAR_PREFIX)) return false;
|
|
7325
|
+
const rest = name.slice(SIDECAR_PREFIX.length);
|
|
7326
|
+
return rest.length > 0 && !rest.includes("/");
|
|
7327
|
+
}
|
|
7328
|
+
function collectSidecarBlobs(files) {
|
|
7329
|
+
const blobs = /* @__PURE__ */ new Map();
|
|
7330
|
+
for (const [name, bytes] of files) {
|
|
7331
|
+
if (isSidecarEntry(name)) {
|
|
7332
|
+
blobs.set(name.slice(SIDECAR_PREFIX.length), bytes);
|
|
7333
|
+
}
|
|
7334
|
+
}
|
|
7335
|
+
return blobs;
|
|
7336
|
+
}
|
|
6801
7337
|
|
|
6802
7338
|
// src/shard/shard-export.ts
|
|
6803
7339
|
var encoder = new TextEncoder();
|
|
@@ -6819,7 +7355,8 @@ async function exportShard(db, options) {
|
|
|
6819
7355
|
noteQuery = `SELECT n.id, n.title, n.format, n.source, n.is_starred, n.is_archived,
|
|
6820
7356
|
n.created_at, n.updated_at, n.deleted_at,
|
|
6821
7357
|
o.content as original_content,
|
|
6822
|
-
c.content as revised_content
|
|
7358
|
+
c.content as revised_content,
|
|
7359
|
+
$1::text as collection_id
|
|
6823
7360
|
FROM note n
|
|
6824
7361
|
LEFT JOIN note_original o ON o.note_id = n.id
|
|
6825
7362
|
LEFT JOIN note_revised_current c ON c.note_id = n.id
|
|
@@ -6831,7 +7368,14 @@ async function exportShard(db, options) {
|
|
|
6831
7368
|
noteQuery = `SELECT n.id, n.title, n.format, n.source, n.is_starred, n.is_archived,
|
|
6832
7369
|
n.created_at, n.updated_at, n.deleted_at,
|
|
6833
7370
|
o.content as original_content,
|
|
6834
|
-
c.content as revised_content
|
|
7371
|
+
c.content as revised_content,
|
|
7372
|
+
(
|
|
7373
|
+
SELECT cn.collection_id
|
|
7374
|
+
FROM collection_note cn
|
|
7375
|
+
WHERE cn.note_id = n.id
|
|
7376
|
+
ORDER BY cn.position, cn.added_at
|
|
7377
|
+
LIMIT 1
|
|
7378
|
+
) as collection_id
|
|
6835
7379
|
FROM note n
|
|
6836
7380
|
LEFT JOIN note_original o ON o.note_id = n.id
|
|
6837
7381
|
LEFT JOIN note_revised_current c ON c.note_id = n.id
|
|
@@ -6843,7 +7387,14 @@ async function exportShard(db, options) {
|
|
|
6843
7387
|
noteQuery = `SELECT n.id, n.title, n.format, n.source, n.is_starred, n.is_archived,
|
|
6844
7388
|
n.created_at, n.updated_at, n.deleted_at,
|
|
6845
7389
|
o.content as original_content,
|
|
6846
|
-
c.content as revised_content
|
|
7390
|
+
c.content as revised_content,
|
|
7391
|
+
(
|
|
7392
|
+
SELECT cn.collection_id
|
|
7393
|
+
FROM collection_note cn
|
|
7394
|
+
WHERE cn.note_id = n.id
|
|
7395
|
+
ORDER BY cn.position, cn.added_at
|
|
7396
|
+
LIMIT 1
|
|
7397
|
+
) as collection_id
|
|
6847
7398
|
FROM note n
|
|
6848
7399
|
LEFT JOIN note_original o ON o.note_id = n.id
|
|
6849
7400
|
LEFT JOIN note_revised_current c ON c.note_id = n.id
|
|
@@ -6875,26 +7426,28 @@ async function exportShard(db, options) {
|
|
|
6875
7426
|
WHERE a.deleted_at IS NULL
|
|
6876
7427
|
ORDER BY a.note_id, a.position, a.created_at`
|
|
6877
7428
|
);
|
|
6878
|
-
const
|
|
7429
|
+
const attachmentsByNote = /* @__PURE__ */ new Map();
|
|
6879
7430
|
for (const row of attachmentRows.rows) {
|
|
6880
7431
|
const source = {
|
|
6881
|
-
extracted_text: row.extracted_text
|
|
7432
|
+
extracted_text: row.extracted_text,
|
|
6882
7433
|
attachment: {
|
|
7434
|
+
// `path` is the display filename per the binary-attachment projection
|
|
7435
|
+
// contract — never the physical storage key (`storage_path`).
|
|
6883
7436
|
id: row.id,
|
|
6884
|
-
path: row.
|
|
7437
|
+
path: row.filename,
|
|
6885
7438
|
mime: row.mime_type,
|
|
6886
7439
|
checksum: row.content_hash,
|
|
6887
7440
|
bytes: Number(row.size_bytes)
|
|
6888
7441
|
}
|
|
6889
7442
|
};
|
|
6890
|
-
const sources =
|
|
7443
|
+
const sources = attachmentsByNote.get(row.note_id) ?? [];
|
|
6891
7444
|
sources.push(source);
|
|
6892
|
-
|
|
7445
|
+
attachmentsByNote.set(row.note_id, sources);
|
|
6893
7446
|
}
|
|
6894
7447
|
const notes = noteRows.rows.map((row) => ({
|
|
6895
7448
|
...row,
|
|
6896
7449
|
tags: tagsByNote.get(row.id) ?? [],
|
|
6897
|
-
|
|
7450
|
+
attachments: attachmentsByNote.get(row.id)
|
|
6898
7451
|
}));
|
|
6899
7452
|
const exportedNoteIds = new Set(notes.map((n) => n.id));
|
|
6900
7453
|
const shardNotes = notes.map((n) => noteToShard(n));
|
|
@@ -6905,7 +7458,7 @@ async function exportShard(db, options) {
|
|
|
6905
7458
|
for (let offset = 0; offset < shardNotes.length; offset += clusterSize) {
|
|
6906
7459
|
const slice = shardNotes.slice(offset, offset + clusterSize);
|
|
6907
7460
|
const href = `notes/${String(offset).padStart(6, "0")}.jsonl`;
|
|
6908
|
-
clusters.push({ href, offset
|
|
7461
|
+
clusters.push({ href, offset });
|
|
6909
7462
|
files.set(href, encoder.encode(slice.map((n) => JSON.stringify(n)).join("\n")));
|
|
6910
7463
|
}
|
|
6911
7464
|
layout = { clusters: { notes: clusters } };
|
|
@@ -6946,14 +7499,27 @@ async function exportShard(db, options) {
|
|
|
6946
7499
|
files.set("tags.json", encoder.encode(JSON.stringify(shardTags)));
|
|
6947
7500
|
components.push("tags");
|
|
6948
7501
|
counts.tags = shardTags.length;
|
|
7502
|
+
const templateRows = await db.query(`SELECT * FROM template ORDER BY created_at, id`);
|
|
7503
|
+
if (templateRows.rows.length > 0) {
|
|
7504
|
+
const shardTemplates = templateRows.rows.map((template) => templateToShard(template));
|
|
7505
|
+
files.set("templates.json", encoder.encode(JSON.stringify(shardTemplates)));
|
|
7506
|
+
components.push("templates");
|
|
7507
|
+
counts.templates = shardTemplates.length;
|
|
7508
|
+
}
|
|
6949
7509
|
const linkRows = await db.query(
|
|
6950
7510
|
`SELECT * FROM link WHERE deleted_at IS NULL ORDER BY created_at`
|
|
6951
7511
|
);
|
|
6952
7512
|
const filteredLinks = options?.tag || options?.collectionId ? linkRows.rows.filter((l) => exportedNoteIds.has(l.source_note_id) && exportedNoteIds.has(l.target_note_id)) : linkRows.rows;
|
|
6953
|
-
const
|
|
7513
|
+
const urlLinkRows = await db.query(`SELECT * FROM link_url_target WHERE deleted_at IS NULL ORDER BY created_at`);
|
|
7514
|
+
const filteredUrlLinks = options?.tag || options?.collectionId ? urlLinkRows.rows.filter((l) => exportedNoteIds.has(l.source_note_id)) : urlLinkRows.rows;
|
|
7515
|
+
const shardLinks = [
|
|
7516
|
+
...filteredLinks.map((l) => linkToShard(l)),
|
|
7517
|
+
...filteredUrlLinks.map((l) => urlLinkToShard(l))
|
|
7518
|
+
];
|
|
7519
|
+
const linksJsonl = shardLinks.map((l) => JSON.stringify(l)).join("\n");
|
|
6954
7520
|
files.set("links.jsonl", encoder.encode(linksJsonl));
|
|
6955
7521
|
components.push("links");
|
|
6956
|
-
counts.links =
|
|
7522
|
+
counts.links = shardLinks.length;
|
|
6957
7523
|
const allNoteSkosRows = await db.query(`SELECT * FROM note_skos_tag ORDER BY created_at`);
|
|
6958
7524
|
const filteredNoteSkosRows = isFiltered ? allNoteSkosRows.rows.filter((row) => exportedNoteIds.has(row.note_id)) : allNoteSkosRows.rows;
|
|
6959
7525
|
const referencedConceptIds = new Set(filteredNoteSkosRows.map((row) => row.concept_id));
|
|
@@ -6994,9 +7560,27 @@ async function exportShard(db, options) {
|
|
|
6994
7560
|
const setScoped = embeddingSetIds.length > 0;
|
|
6995
7561
|
const includeMaterializedSelectors = options.includeMaterializedSelectors === true;
|
|
6996
7562
|
const embSetRows = await db.query(
|
|
6997
|
-
`SELECT
|
|
6998
|
-
|
|
6999
|
-
|
|
7563
|
+
`SELECT
|
|
7564
|
+
es.id, es.name, es.slug, es.description, es.purpose,
|
|
7565
|
+
COALESCE(es.document_count, member_counts.document_count, 0)::int AS document_count,
|
|
7566
|
+
COALESCE(es.embedding_count, embedding_counts.embedding_count, 0)::int AS embedding_count,
|
|
7567
|
+
es.is_system, es.keywords_json,
|
|
7568
|
+
es.model_name, es.dimensions, es.kind, es.mode, es.truncate_dimension,
|
|
7569
|
+
es.criteria_json, es.source_json, es.compatibility_json, es.materialization_json,
|
|
7570
|
+
es.freshness_json, es.created_at, es.updated_at
|
|
7571
|
+
FROM embedding_set es
|
|
7572
|
+
LEFT JOIN (
|
|
7573
|
+
SELECT embedding_set_id, COUNT(*)::int AS document_count
|
|
7574
|
+
FROM embedding_set_member
|
|
7575
|
+
GROUP BY embedding_set_id
|
|
7576
|
+
) member_counts ON member_counts.embedding_set_id = es.id
|
|
7577
|
+
LEFT JOIN (
|
|
7578
|
+
SELECT embedding_set_id, COUNT(*)::int AS embedding_count
|
|
7579
|
+
FROM embedding
|
|
7580
|
+
GROUP BY embedding_set_id
|
|
7581
|
+
) embedding_counts ON embedding_counts.embedding_set_id = es.id
|
|
7582
|
+
${setScoped ? "WHERE es.id = ANY($1)" : ""}
|
|
7583
|
+
ORDER BY es.created_at`,
|
|
7000
7584
|
setScoped ? [embeddingSetIds] : []
|
|
7001
7585
|
);
|
|
7002
7586
|
const exportedSetIds = new Set(embSetRows.rows.map((row) => row.id));
|
|
@@ -7011,6 +7595,17 @@ async function exportShard(db, options) {
|
|
|
7011
7595
|
files.set("embedding_sets.json", encoder.encode(JSON.stringify(shardEmbSets)));
|
|
7012
7596
|
components.push("embedding_sets");
|
|
7013
7597
|
counts.embedding_sets = shardEmbSets.length;
|
|
7598
|
+
const embeddingConfigRows = await db.query(
|
|
7599
|
+
`SELECT id, name, description, model, dimension, chunk_size, chunk_overlap, is_default
|
|
7600
|
+
FROM embedding_config
|
|
7601
|
+
ORDER BY name, id`
|
|
7602
|
+
);
|
|
7603
|
+
if (embeddingConfigRows.rows.length > 0) {
|
|
7604
|
+
const shardEmbeddingConfigs = embeddingConfigRows.rows.map((row) => embeddingConfigToShard(row));
|
|
7605
|
+
files.set("embedding_configs.json", encoder.encode(JSON.stringify(shardEmbeddingConfigs)));
|
|
7606
|
+
components.push("embedding_configs");
|
|
7607
|
+
counts.embedding_configs = shardEmbeddingConfigs.length;
|
|
7608
|
+
}
|
|
7014
7609
|
const embMemberRows = await db.query(
|
|
7015
7610
|
`SELECT * FROM embedding_set_member
|
|
7016
7611
|
${setScoped ? "WHERE embedding_set_id = ANY($1)" : ""}`,
|
|
@@ -7024,12 +7619,24 @@ async function exportShard(db, options) {
|
|
|
7024
7619
|
components.push("embedding_set_members");
|
|
7025
7620
|
counts.embedding_set_members = scopedEmbMemberRows.length;
|
|
7026
7621
|
const embRows = await db.query(
|
|
7027
|
-
`SELECT
|
|
7028
|
-
|
|
7029
|
-
|
|
7622
|
+
`SELECT
|
|
7623
|
+
e.id, e.note_id, e.embedding_set_id, e.chunk_index,
|
|
7624
|
+
COALESCE(NULLIF(e.text, ''), nrc.content, no.content, '') AS text,
|
|
7625
|
+
e.vector,
|
|
7626
|
+
COALESCE(e.model, es.model_name) AS model,
|
|
7627
|
+
es.model_name,
|
|
7628
|
+
e.created_at
|
|
7629
|
+
FROM embedding e
|
|
7630
|
+
JOIN embedding_set es ON es.id = e.embedding_set_id
|
|
7631
|
+
LEFT JOIN note_revised_current nrc ON nrc.note_id = e.note_id
|
|
7632
|
+
LEFT JOIN note_original no ON no.note_id = e.note_id
|
|
7633
|
+
${setScoped ? "WHERE e.embedding_set_id = ANY($1)" : ""}
|
|
7634
|
+
ORDER BY e.created_at`,
|
|
7030
7635
|
setScoped ? [embeddingSetIds] : []
|
|
7031
7636
|
);
|
|
7032
|
-
const memberEmbeddingIds = new Set(
|
|
7637
|
+
const memberEmbeddingIds = new Set(
|
|
7638
|
+
scopedEmbMemberRows.map((member) => member.embedding_id).filter((id) => Boolean(id))
|
|
7639
|
+
);
|
|
7033
7640
|
const scopedEmbRows = embRows.rows.filter(
|
|
7034
7641
|
(embedding) => exportedSetIds.has(embedding.embedding_set_id) && exportedNoteIds.has(embedding.note_id) && (memberEmbeddingIds.size === 0 || memberEmbeddingIds.has(embedding.id))
|
|
7035
7642
|
);
|
|
@@ -7145,14 +7752,37 @@ async function exportShard(db, options) {
|
|
|
7145
7752
|
counts,
|
|
7146
7753
|
checksums,
|
|
7147
7754
|
min_reader_version: "1.0.0",
|
|
7755
|
+
migrated_from: null,
|
|
7756
|
+
migration_history: [],
|
|
7148
7757
|
...layout ? { layout } : {}
|
|
7149
7758
|
};
|
|
7150
7759
|
files.set("manifest.json", encoder.encode(JSON.stringify(manifest, null, 2)));
|
|
7760
|
+
if (options?.includeBlobs && options.blobStore) {
|
|
7761
|
+
const packed = /* @__PURE__ */ new Set();
|
|
7762
|
+
for (const row of attachmentRows.rows) {
|
|
7763
|
+
const checksum = row.content_hash;
|
|
7764
|
+
if (packed.has(checksum)) continue;
|
|
7765
|
+
packed.add(checksum);
|
|
7766
|
+
const bytes = await options.blobStore.read(checksum);
|
|
7767
|
+
if (bytes) files.set(sidecarEntryName(checksum), bytes);
|
|
7768
|
+
}
|
|
7769
|
+
}
|
|
7151
7770
|
return packTarGz(files);
|
|
7152
7771
|
}
|
|
7153
7772
|
|
|
7154
|
-
// src/shard/
|
|
7773
|
+
// src/shard/parse.ts
|
|
7155
7774
|
var decoder = new TextDecoder();
|
|
7775
|
+
function parseJsonlBytes(data) {
|
|
7776
|
+
if (!data || data.byteLength === 0) return [];
|
|
7777
|
+
return decoder.decode(data).split("\n").filter((line) => line.trim()).map((line) => JSON.parse(line));
|
|
7778
|
+
}
|
|
7779
|
+
function parseJsonArrayBytes(data) {
|
|
7780
|
+
if (!data || data.byteLength === 0) return [];
|
|
7781
|
+
return JSON.parse(decoder.decode(data));
|
|
7782
|
+
}
|
|
7783
|
+
|
|
7784
|
+
// src/shard/shard-import.ts
|
|
7785
|
+
var decoder2 = new TextDecoder();
|
|
7156
7786
|
var DEFAULT_BATCH_SIZE = 250;
|
|
7157
7787
|
async function yieldToEventLoop2() {
|
|
7158
7788
|
const scheduler = globalThis.scheduler;
|
|
@@ -7174,12 +7804,17 @@ async function importShard(db, data, options) {
|
|
|
7174
7804
|
const report = options?.onProgress;
|
|
7175
7805
|
const warnings = [];
|
|
7176
7806
|
const errors = [];
|
|
7807
|
+
let importedAttachmentReferenceCount = 0;
|
|
7808
|
+
let notesWithImportedAttachmentReferences = 0;
|
|
7809
|
+
const blobsToHydrate = /* @__PURE__ */ new Map();
|
|
7177
7810
|
const counts = {
|
|
7178
7811
|
notes: 0,
|
|
7179
7812
|
collections: 0,
|
|
7813
|
+
templates: 0,
|
|
7180
7814
|
tags: 0,
|
|
7181
7815
|
links: 0,
|
|
7182
7816
|
embedding_sets: 0,
|
|
7817
|
+
embedding_configs: 0,
|
|
7183
7818
|
embedding_set_members: 0,
|
|
7184
7819
|
embeddings: 0,
|
|
7185
7820
|
skos_schemes: 0,
|
|
@@ -7223,7 +7858,10 @@ async function importShard(db, data, options) {
|
|
|
7223
7858
|
}
|
|
7224
7859
|
let manifest;
|
|
7225
7860
|
try {
|
|
7226
|
-
manifest = JSON.parse(
|
|
7861
|
+
manifest = JSON.parse(decoder2.decode(manifestData));
|
|
7862
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
7863
|
+
throw new Error("manifest must be a JSON object");
|
|
7864
|
+
}
|
|
7227
7865
|
} catch {
|
|
7228
7866
|
return {
|
|
7229
7867
|
success: false,
|
|
@@ -7234,7 +7872,7 @@ async function importShard(db, data, options) {
|
|
|
7234
7872
|
duration_ms: performance.now() - start
|
|
7235
7873
|
};
|
|
7236
7874
|
}
|
|
7237
|
-
if (manifest.min_reader_version && manifest.min_reader_version >
|
|
7875
|
+
if (manifest.min_reader_version && compareShardVersions(manifest.min_reader_version, CURRENT_SHARD_VERSION) > 0) {
|
|
7238
7876
|
return {
|
|
7239
7877
|
success: false,
|
|
7240
7878
|
counts,
|
|
@@ -7247,6 +7885,16 @@ async function importShard(db, data, options) {
|
|
|
7247
7885
|
};
|
|
7248
7886
|
}
|
|
7249
7887
|
report?.({ phase: "validate", done: 0, total: 1 });
|
|
7888
|
+
if (!manifest.checksums || typeof manifest.checksums !== "object") {
|
|
7889
|
+
return {
|
|
7890
|
+
success: false,
|
|
7891
|
+
counts,
|
|
7892
|
+
skipped,
|
|
7893
|
+
warnings,
|
|
7894
|
+
errors: ["Invalid manifest.json: checksums must be an object"],
|
|
7895
|
+
duration_ms: performance.now() - start
|
|
7896
|
+
};
|
|
7897
|
+
}
|
|
7250
7898
|
const checksumResult = await validateChecksums(manifest.checksums, files);
|
|
7251
7899
|
if (!checksumResult.valid) {
|
|
7252
7900
|
return {
|
|
@@ -7259,25 +7907,61 @@ async function importShard(db, data, options) {
|
|
|
7259
7907
|
};
|
|
7260
7908
|
}
|
|
7261
7909
|
report?.({ phase: "validate", done: 1, total: 1 });
|
|
7910
|
+
const sidecarBlobs = options?.blobStore ? collectSidecarBlobs(files) : null;
|
|
7262
7911
|
const noteClusters = manifest.layout?.clusters?.notes;
|
|
7263
|
-
const
|
|
7264
|
-
|
|
7265
|
-
|
|
7266
|
-
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
|
|
7277
|
-
|
|
7278
|
-
|
|
7279
|
-
|
|
7280
|
-
|
|
7912
|
+
const parseComponent = (name, parse) => {
|
|
7913
|
+
try {
|
|
7914
|
+
return parse();
|
|
7915
|
+
} catch (err) {
|
|
7916
|
+
throw new Error(`${name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
7917
|
+
}
|
|
7918
|
+
};
|
|
7919
|
+
let parsedNotes;
|
|
7920
|
+
let parsedCollections;
|
|
7921
|
+
let parsedTemplates;
|
|
7922
|
+
let parsedLinks;
|
|
7923
|
+
let parsedEmbSets;
|
|
7924
|
+
let parsedEmbConfigs;
|
|
7925
|
+
let parsedEmbMembers;
|
|
7926
|
+
let parsedEmbeddings;
|
|
7927
|
+
let parsedSkosSchemes;
|
|
7928
|
+
let parsedSkosConcepts;
|
|
7929
|
+
let parsedSkosRelations;
|
|
7930
|
+
let parsedNoteSkosTags;
|
|
7931
|
+
let parsedProvenanceEdges;
|
|
7932
|
+
let parsedGraphSources;
|
|
7933
|
+
let parsedGraphEdges;
|
|
7934
|
+
let parsedCommunitySets;
|
|
7935
|
+
let parsedCommunityAssignments;
|
|
7936
|
+
try {
|
|
7937
|
+
parsedNotes = parseComponent("notes", () => noteClusters && noteClusters.length > 0 ? [...noteClusters].sort((a, b) => a.offset - b.offset).flatMap((ref) => parseJsonlBytes(files.get(ref.href))) : parseJsonlBytes(files.get("notes.jsonl")));
|
|
7938
|
+
parsedCollections = parseComponent("collections.json", () => parseJsonArrayBytes(files.get("collections.json")));
|
|
7939
|
+
parseComponent("tags.json", () => parseJsonArrayBytes(files.get("tags.json")));
|
|
7940
|
+
parsedTemplates = parseComponent("templates.json", () => parseJsonArrayBytes(files.get("templates.json")));
|
|
7941
|
+
parsedLinks = parseComponent("links.jsonl", () => parseJsonlBytes(files.get("links.jsonl")));
|
|
7942
|
+
parsedEmbSets = parseComponent("embedding_sets.json", () => parseJsonArrayBytes(files.get("embedding_sets.json")));
|
|
7943
|
+
parsedEmbConfigs = parseComponent("embedding_configs.json", () => parseJsonArrayBytes(files.get("embedding_configs.json")));
|
|
7944
|
+
parsedEmbMembers = parseComponent("embedding_set_members.jsonl", () => parseJsonlBytes(files.get("embedding_set_members.jsonl")));
|
|
7945
|
+
parsedEmbeddings = parseComponent("embeddings.jsonl", () => parseJsonlBytes(files.get("embeddings.jsonl")));
|
|
7946
|
+
parsedSkosSchemes = parseComponent("skos_schemes.json", () => parseJsonArrayBytes(files.get("skos_schemes.json")));
|
|
7947
|
+
parsedSkosConcepts = parseComponent("skos_concepts.json", () => parseJsonArrayBytes(files.get("skos_concepts.json")));
|
|
7948
|
+
parsedSkosRelations = parseComponent("skos_relations.jsonl", () => parseJsonlBytes(files.get("skos_relations.jsonl")));
|
|
7949
|
+
parsedNoteSkosTags = parseComponent("note_skos_tags.jsonl", () => parseJsonlBytes(files.get("note_skos_tags.jsonl")));
|
|
7950
|
+
parsedProvenanceEdges = parseComponent("provenance_edges.jsonl", () => parseJsonlBytes(files.get("provenance_edges.jsonl")));
|
|
7951
|
+
parsedGraphSources = parseComponent("graph_sources.json", () => parseJsonArrayBytes(files.get("graph_sources.json")));
|
|
7952
|
+
parsedGraphEdges = parseComponent("graph_edges.jsonl", () => parseJsonlBytes(files.get("graph_edges.jsonl")));
|
|
7953
|
+
parsedCommunitySets = parseComponent("communities.json", () => parseJsonArrayBytes(files.get("communities.json")));
|
|
7954
|
+
parsedCommunityAssignments = parseComponent("community_assignments.jsonl", () => parseJsonlBytes(files.get("community_assignments.jsonl")));
|
|
7955
|
+
} catch (err) {
|
|
7956
|
+
return {
|
|
7957
|
+
success: false,
|
|
7958
|
+
counts,
|
|
7959
|
+
skipped,
|
|
7960
|
+
warnings,
|
|
7961
|
+
errors: [`Failed to parse shard component: ${err instanceof Error ? err.message : String(err)}`],
|
|
7962
|
+
duration_ms: performance.now() - start
|
|
7963
|
+
};
|
|
7964
|
+
}
|
|
7281
7965
|
const knownFiles = /* @__PURE__ */ new Set([
|
|
7282
7966
|
"manifest.json",
|
|
7283
7967
|
"notes.jsonl",
|
|
@@ -7286,7 +7970,6 @@ async function importShard(db, data, options) {
|
|
|
7286
7970
|
"links.jsonl",
|
|
7287
7971
|
"embedding_sets.json",
|
|
7288
7972
|
"embedding_set_members.jsonl",
|
|
7289
|
-
"embedding_configs.json",
|
|
7290
7973
|
"embeddings.jsonl",
|
|
7291
7974
|
"templates.json",
|
|
7292
7975
|
"skos_schemes.json",
|
|
@@ -7304,15 +7987,36 @@ async function importShard(db, data, options) {
|
|
|
7304
7987
|
warnings.push(`Unknown component skipped: ${filename}`);
|
|
7305
7988
|
}
|
|
7306
7989
|
}
|
|
7307
|
-
if (files.has("templates.json")) {
|
|
7308
|
-
warnings.push("templates.json skipped (not supported in browser)");
|
|
7309
|
-
}
|
|
7310
7990
|
const conflictClause = strategy === "skip" ? "ON CONFLICT DO NOTHING" : "";
|
|
7311
7991
|
try {
|
|
7312
7992
|
await db.transaction(async (tx) => {
|
|
7993
|
+
const preexistingEmbeddingMembers = /* @__PURE__ */ new Set();
|
|
7994
|
+
if (strategy === "skip") {
|
|
7995
|
+
for (const member of parsedEmbMembers) {
|
|
7996
|
+
const existing = await tx.query(
|
|
7997
|
+
"SELECT 1 FROM embedding_set_member WHERE embedding_set_id = $1 AND note_id = $2",
|
|
7998
|
+
[member.embedding_set_id, member.note_id]
|
|
7999
|
+
);
|
|
8000
|
+
if (existing.rows.length > 0) {
|
|
8001
|
+
preexistingEmbeddingMembers.add(`${member.embedding_set_id}\0${member.note_id}`);
|
|
8002
|
+
}
|
|
8003
|
+
}
|
|
8004
|
+
}
|
|
8005
|
+
const skipExisting = async (key, sql, params) => {
|
|
8006
|
+
if (strategy !== "skip") return false;
|
|
8007
|
+
const existing = await tx.query(sql, params);
|
|
8008
|
+
if (existing.rows.length === 0) return false;
|
|
8009
|
+
skipped[key] = (skipped[key] ?? 0) + 1;
|
|
8010
|
+
return true;
|
|
8011
|
+
};
|
|
7313
8012
|
report?.({ phase: "collections", done: 0, total: parsedCollections.length });
|
|
7314
8013
|
for (const [index, shardCol] of parsedCollections.entries()) {
|
|
7315
8014
|
const col = collectionFromShard(shardCol);
|
|
8015
|
+
if (await skipExisting("collections", "SELECT 1 FROM collection WHERE id = $1", [col.id])) {
|
|
8016
|
+
report?.({ phase: "collections", done: index + 1, total: parsedCollections.length });
|
|
8017
|
+
await maybeYield2(index + 1, batchSize);
|
|
8018
|
+
continue;
|
|
8019
|
+
}
|
|
7316
8020
|
if (strategy === "replace") {
|
|
7317
8021
|
await tx.query(
|
|
7318
8022
|
`INSERT INTO collection (id, name, description, parent_id, created_at)
|
|
@@ -7331,9 +8035,38 @@ async function importShard(db, data, options) {
|
|
|
7331
8035
|
report?.({ phase: "collections", done: index + 1, total: parsedCollections.length });
|
|
7332
8036
|
await maybeYield2(index + 1, batchSize);
|
|
7333
8037
|
}
|
|
8038
|
+
report?.({ phase: "templates", done: 0, total: parsedTemplates.length });
|
|
8039
|
+
for (const [index, template] of parsedTemplates.entries()) {
|
|
8040
|
+
await tx.query(
|
|
8041
|
+
`INSERT INTO template (
|
|
8042
|
+
id, name, description, content, format, default_tags, collection_id, created_at, updated_at
|
|
8043
|
+
)
|
|
8044
|
+
VALUES ($1, $2, $3, $4, $5, $6::jsonb, $7, $8, $9)
|
|
8045
|
+
${strategy === "replace" ? "ON CONFLICT (id) DO UPDATE SET name = $2, description = $3, content = $4, format = $5, default_tags = $6::jsonb, collection_id = $7, created_at = $8, updated_at = $9" : conflictClause}`,
|
|
8046
|
+
[
|
|
8047
|
+
template.id,
|
|
8048
|
+
template.name,
|
|
8049
|
+
template.description,
|
|
8050
|
+
template.content,
|
|
8051
|
+
template.format,
|
|
8052
|
+
JSON.stringify(template.default_tags),
|
|
8053
|
+
template.collection_id,
|
|
8054
|
+
template.created_at,
|
|
8055
|
+
template.updated_at
|
|
8056
|
+
]
|
|
8057
|
+
);
|
|
8058
|
+
counts.templates++;
|
|
8059
|
+
report?.({ phase: "templates", done: index + 1, total: parsedTemplates.length });
|
|
8060
|
+
await maybeYield2(index + 1, batchSize);
|
|
8061
|
+
}
|
|
7334
8062
|
report?.({ phase: "notes", done: 0, total: parsedNotes.length });
|
|
7335
8063
|
for (const [index, shardNote] of parsedNotes.entries()) {
|
|
7336
8064
|
const note = noteFromShard(shardNote);
|
|
8065
|
+
if (await skipExisting("notes", "SELECT 1 FROM note WHERE id = $1", [note.id])) {
|
|
8066
|
+
report?.({ phase: "notes", done: index + 1, total: parsedNotes.length });
|
|
8067
|
+
await maybeYield2(index + 1, batchSize);
|
|
8068
|
+
continue;
|
|
8069
|
+
}
|
|
7337
8070
|
const contentHash = computeHash(new TextEncoder().encode(note.original_content));
|
|
7338
8071
|
if (strategy === "replace") {
|
|
7339
8072
|
await tx.query(
|
|
@@ -7408,14 +8141,98 @@ async function importShard(db, data, options) {
|
|
|
7408
8141
|
[generateId(), note.id, tag]
|
|
7409
8142
|
);
|
|
7410
8143
|
}
|
|
8144
|
+
if (note.collection_id) {
|
|
8145
|
+
await tx.query(
|
|
8146
|
+
`INSERT INTO collection_note (collection_id, note_id)
|
|
8147
|
+
VALUES ($1, $2)
|
|
8148
|
+
ON CONFLICT (collection_id, note_id) DO NOTHING`,
|
|
8149
|
+
[note.collection_id, note.id]
|
|
8150
|
+
);
|
|
8151
|
+
}
|
|
8152
|
+
if (note.attachments?.length) {
|
|
8153
|
+
notesWithImportedAttachmentReferences++;
|
|
8154
|
+
for (let position = 0; position < note.attachments.length; position += 1) {
|
|
8155
|
+
const projection = note.attachments[position];
|
|
8156
|
+
const ref = projection.attachment;
|
|
8157
|
+
const existingBlob = await tx.query(
|
|
8158
|
+
`SELECT id FROM attachment_blob WHERE content_hash = $1`,
|
|
8159
|
+
[ref.checksum]
|
|
8160
|
+
);
|
|
8161
|
+
const blobId = existingBlob.rows[0]?.id ?? generateId();
|
|
8162
|
+
if (!existingBlob.rows.length) {
|
|
8163
|
+
await tx.query(
|
|
8164
|
+
`INSERT INTO attachment_blob (id, content_hash, size_bytes, storage_path)
|
|
8165
|
+
VALUES ($1, $2, $3, $4)
|
|
8166
|
+
ON CONFLICT (content_hash) DO NOTHING`,
|
|
8167
|
+
// storage_path stays NULL: the browser edition addresses blobs
|
|
8168
|
+
// by content_hash via the BlobStore, not a filesystem key, and
|
|
8169
|
+
// `ref.path` is the display filename (never a storage locator).
|
|
8170
|
+
[blobId, ref.checksum, ref.bytes, null]
|
|
8171
|
+
);
|
|
8172
|
+
}
|
|
8173
|
+
const filename = ref.path.split("/").filter(Boolean).pop() ?? ref.path;
|
|
8174
|
+
if (strategy === "replace") {
|
|
8175
|
+
await tx.query(
|
|
8176
|
+
`INSERT INTO attachment (id, note_id, blob_id, filename, mime_type, extracted_text, position)
|
|
8177
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
8178
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
8179
|
+
note_id = $2,
|
|
8180
|
+
blob_id = $3,
|
|
8181
|
+
filename = $4,
|
|
8182
|
+
mime_type = $5,
|
|
8183
|
+
extracted_text = $6,
|
|
8184
|
+
position = $7,
|
|
8185
|
+
deleted_at = NULL`,
|
|
8186
|
+
[ref.id, note.id, blobId, filename, ref.mime, projection.extracted_text, position]
|
|
8187
|
+
);
|
|
8188
|
+
} else {
|
|
8189
|
+
await tx.query(
|
|
8190
|
+
`INSERT INTO attachment (id, note_id, blob_id, filename, mime_type, extracted_text, position)
|
|
8191
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7) ${conflictClause}`,
|
|
8192
|
+
[ref.id, note.id, blobId, filename, ref.mime, projection.extracted_text, position]
|
|
8193
|
+
);
|
|
8194
|
+
}
|
|
8195
|
+
let hydrated = false;
|
|
8196
|
+
if (sidecarBlobs) {
|
|
8197
|
+
if (blobsToHydrate.has(ref.checksum)) {
|
|
8198
|
+
hydrated = true;
|
|
8199
|
+
} else {
|
|
8200
|
+
const bytes = sidecarBlobs.get(blobChecksumToHex(ref.checksum));
|
|
8201
|
+
if (bytes) {
|
|
8202
|
+
if (computeBlobHash(bytes) === ref.checksum) {
|
|
8203
|
+
blobsToHydrate.set(ref.checksum, bytes);
|
|
8204
|
+
hydrated = true;
|
|
8205
|
+
} else {
|
|
8206
|
+
warnings.push(
|
|
8207
|
+
`Sidecar blob for attachment ${ref.id} failed BLAKE3 integrity check (expected ${ref.checksum}); imported as reference-only.`
|
|
8208
|
+
);
|
|
8209
|
+
}
|
|
8210
|
+
}
|
|
8211
|
+
}
|
|
8212
|
+
}
|
|
8213
|
+
if (!hydrated) {
|
|
8214
|
+
importedAttachmentReferenceCount++;
|
|
8215
|
+
}
|
|
8216
|
+
}
|
|
8217
|
+
}
|
|
7411
8218
|
counts.notes++;
|
|
7412
8219
|
report?.({ phase: "notes", done: index + 1, total: parsedNotes.length });
|
|
7413
8220
|
await maybeYield2(index + 1, batchSize);
|
|
7414
8221
|
}
|
|
8222
|
+
if (importedAttachmentReferenceCount > 0) {
|
|
8223
|
+
warnings.push(
|
|
8224
|
+
`${importedAttachmentReferenceCount} attachment reference(s) across ${notesWithImportedAttachmentReferences} note(s) were imported as metadata only: these shard records carry extracted text plus attachment metadata but no matching byte-sidecar entry, so their BlobStore bytes are unavailable (getBlob() returns null). Export a self-contained shard (\`includeBlobs\` + a \`blobStore\`) and import with a \`blobStore\` to hydrate bytes. Tracking: #271 / server #1046.`
|
|
8225
|
+
);
|
|
8226
|
+
}
|
|
7415
8227
|
const totalSkos = parsedSkosSchemes.length + parsedSkosConcepts.length + parsedSkosRelations.length + parsedNoteSkosTags.length;
|
|
7416
8228
|
let doneSkos = 0;
|
|
7417
8229
|
report?.({ phase: "skos", done: doneSkos, total: totalSkos });
|
|
7418
8230
|
for (const scheme of parsedSkosSchemes) {
|
|
8231
|
+
if (await skipExisting("skos_schemes", "SELECT 1 FROM skos_scheme WHERE id = $1", [scheme.id])) {
|
|
8232
|
+
report?.({ phase: "skos", done: ++doneSkos, total: totalSkos });
|
|
8233
|
+
await maybeYield2(doneSkos, batchSize);
|
|
8234
|
+
continue;
|
|
8235
|
+
}
|
|
7419
8236
|
if (strategy === "replace") {
|
|
7420
8237
|
await tx.query(
|
|
7421
8238
|
`INSERT INTO skos_scheme (id, title, description, created_at, updated_at)
|
|
@@ -7435,6 +8252,11 @@ async function importShard(db, data, options) {
|
|
|
7435
8252
|
await maybeYield2(doneSkos, batchSize);
|
|
7436
8253
|
}
|
|
7437
8254
|
for (const concept of parsedSkosConcepts) {
|
|
8255
|
+
if (await skipExisting("skos_concepts", "SELECT 1 FROM skos_concept WHERE id = $1", [concept.id])) {
|
|
8256
|
+
report?.({ phase: "skos", done: ++doneSkos, total: totalSkos });
|
|
8257
|
+
await maybeYield2(doneSkos, batchSize);
|
|
8258
|
+
continue;
|
|
8259
|
+
}
|
|
7438
8260
|
const altLabels = JSON.stringify(concept.alt_labels ?? []);
|
|
7439
8261
|
if (strategy === "replace") {
|
|
7440
8262
|
await tx.query(
|
|
@@ -7457,6 +8279,33 @@ async function importShard(db, data, options) {
|
|
|
7457
8279
|
report?.({ phase: "links", done: 0, total: parsedLinks.length });
|
|
7458
8280
|
for (const [index, shardLink] of parsedLinks.entries()) {
|
|
7459
8281
|
const link = linkFromShard(shardLink);
|
|
8282
|
+
if (!link.target_note_id) {
|
|
8283
|
+
if (!link.to_url) {
|
|
8284
|
+
skipped.links = (skipped.links ?? 0) + 1;
|
|
8285
|
+
warnings.push(`Shard link skipped: ${link.id} has neither to_note_id nor to_url.`);
|
|
8286
|
+
report?.({ phase: "links", done: index + 1, total: parsedLinks.length });
|
|
8287
|
+
await maybeYield2(index + 1, batchSize);
|
|
8288
|
+
continue;
|
|
8289
|
+
}
|
|
8290
|
+
const metadata = link.metadata == null ? null : JSON.stringify(link.metadata);
|
|
8291
|
+
await tx.query(
|
|
8292
|
+
`INSERT INTO link_url_target (
|
|
8293
|
+
id, source_note_id, to_url, link_type, confidence, metadata_json, created_at
|
|
8294
|
+
)
|
|
8295
|
+
VALUES ($1, $2, $3, $4, $5, $6::jsonb, $7)
|
|
8296
|
+
${strategy === "replace" ? "ON CONFLICT (id) DO UPDATE SET source_note_id = $2, to_url = $3, link_type = $4, confidence = $5, metadata_json = $6::jsonb, created_at = $7" : conflictClause}`,
|
|
8297
|
+
[link.id, link.source_note_id, link.to_url, link.link_type, link.confidence, metadata, link.created_at]
|
|
8298
|
+
);
|
|
8299
|
+
counts.links++;
|
|
8300
|
+
report?.({ phase: "links", done: index + 1, total: parsedLinks.length });
|
|
8301
|
+
await maybeYield2(index + 1, batchSize);
|
|
8302
|
+
continue;
|
|
8303
|
+
}
|
|
8304
|
+
if (await skipExisting("links", "SELECT 1 FROM link WHERE id = $1", [link.id])) {
|
|
8305
|
+
report?.({ phase: "links", done: index + 1, total: parsedLinks.length });
|
|
8306
|
+
await maybeYield2(index + 1, batchSize);
|
|
8307
|
+
continue;
|
|
8308
|
+
}
|
|
7460
8309
|
if (strategy === "replace") {
|
|
7461
8310
|
await tx.query(
|
|
7462
8311
|
`INSERT INTO link (id, source_note_id, target_note_id, link_type, confidence, created_at)
|
|
@@ -7476,6 +8325,11 @@ async function importShard(db, data, options) {
|
|
|
7476
8325
|
await maybeYield2(index + 1, batchSize);
|
|
7477
8326
|
}
|
|
7478
8327
|
for (const relation of parsedSkosRelations) {
|
|
8328
|
+
if (await skipExisting("skos_relations", "SELECT 1 FROM skos_concept_relation WHERE id = $1", [relation.id])) {
|
|
8329
|
+
report?.({ phase: "skos", done: ++doneSkos, total: totalSkos });
|
|
8330
|
+
await maybeYield2(doneSkos, batchSize);
|
|
8331
|
+
continue;
|
|
8332
|
+
}
|
|
7479
8333
|
if (strategy === "replace") {
|
|
7480
8334
|
await tx.query(
|
|
7481
8335
|
`INSERT INTO skos_concept_relation (id, source_concept_id, target_concept_id, relation_type, created_at)
|
|
@@ -7495,6 +8349,11 @@ async function importShard(db, data, options) {
|
|
|
7495
8349
|
await maybeYield2(doneSkos, batchSize);
|
|
7496
8350
|
}
|
|
7497
8351
|
for (const tag of parsedNoteSkosTags) {
|
|
8352
|
+
if (await skipExisting("note_skos_tags", "SELECT 1 FROM note_skos_tag WHERE note_id = $1 AND concept_id = $2", [tag.note_id, tag.concept_id])) {
|
|
8353
|
+
report?.({ phase: "skos", done: ++doneSkos, total: totalSkos });
|
|
8354
|
+
await maybeYield2(doneSkos, batchSize);
|
|
8355
|
+
continue;
|
|
8356
|
+
}
|
|
7498
8357
|
await tx.query(
|
|
7499
8358
|
`INSERT INTO note_skos_tag (id, note_id, concept_id, created_at)
|
|
7500
8359
|
VALUES ($1, $2, $3, $4)
|
|
@@ -7507,6 +8366,11 @@ async function importShard(db, data, options) {
|
|
|
7507
8366
|
}
|
|
7508
8367
|
report?.({ phase: "provenance", done: 0, total: parsedProvenanceEdges.length });
|
|
7509
8368
|
for (const [index, edge] of parsedProvenanceEdges.entries()) {
|
|
8369
|
+
if (await skipExisting("provenance_edges", "SELECT 1 FROM provenance_edge WHERE id = $1", [edge.id])) {
|
|
8370
|
+
report?.({ phase: "provenance", done: index + 1, total: parsedProvenanceEdges.length });
|
|
8371
|
+
await maybeYield2(index + 1, batchSize);
|
|
8372
|
+
continue;
|
|
8373
|
+
}
|
|
7510
8374
|
const attributes = edge.attributes === null ? null : JSON.stringify(edge.attributes);
|
|
7511
8375
|
if (strategy === "replace") {
|
|
7512
8376
|
await tx.query(
|
|
@@ -7526,27 +8390,59 @@ async function importShard(db, data, options) {
|
|
|
7526
8390
|
report?.({ phase: "provenance", done: index + 1, total: parsedProvenanceEdges.length });
|
|
7527
8391
|
await maybeYield2(index + 1, batchSize);
|
|
7528
8392
|
}
|
|
8393
|
+
report?.({ phase: "embedding_configs", done: 0, total: parsedEmbConfigs.length });
|
|
8394
|
+
for (const [index, config] of parsedEmbConfigs.entries()) {
|
|
8395
|
+
await tx.query(
|
|
8396
|
+
`INSERT INTO embedding_config (
|
|
8397
|
+
id, name, description, model, dimension, chunk_size, chunk_overlap, is_default
|
|
8398
|
+
)
|
|
8399
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
8400
|
+
${strategy === "replace" ? "ON CONFLICT (id) DO UPDATE SET name = $2, description = $3, model = $4, dimension = $5, chunk_size = $6, chunk_overlap = $7, is_default = $8" : conflictClause}`,
|
|
8401
|
+
[
|
|
8402
|
+
config.id,
|
|
8403
|
+
config.name,
|
|
8404
|
+
config.description,
|
|
8405
|
+
config.model,
|
|
8406
|
+
config.dimension,
|
|
8407
|
+
config.chunk_size,
|
|
8408
|
+
config.chunk_overlap,
|
|
8409
|
+
config.is_default
|
|
8410
|
+
]
|
|
8411
|
+
);
|
|
8412
|
+
counts.embedding_configs++;
|
|
8413
|
+
report?.({ phase: "embedding_configs", done: index + 1, total: parsedEmbConfigs.length });
|
|
8414
|
+
await maybeYield2(index + 1, batchSize);
|
|
8415
|
+
}
|
|
7529
8416
|
report?.({ phase: "embedding_sets", done: 0, total: parsedEmbSets.length });
|
|
7530
8417
|
for (const [index, shardSet] of parsedEmbSets.entries()) {
|
|
7531
|
-
const set = embeddingSetFromShard(shardSet);
|
|
8418
|
+
const set = embeddingSetFromShard(shardSet, manifest.created_at);
|
|
8419
|
+
if (await skipExisting("embedding_sets", "SELECT 1 FROM embedding_set WHERE id = $1", [set.id])) {
|
|
8420
|
+
report?.({ phase: "embedding_sets", done: index + 1, total: parsedEmbSets.length });
|
|
8421
|
+
await maybeYield2(index + 1, batchSize);
|
|
8422
|
+
continue;
|
|
8423
|
+
}
|
|
7532
8424
|
if (strategy === "replace") {
|
|
7533
8425
|
await tx.query(
|
|
7534
8426
|
`INSERT INTO embedding_set (
|
|
7535
|
-
id, name,
|
|
8427
|
+
id, name, slug, description, purpose, document_count, embedding_count, is_system, keywords_json,
|
|
8428
|
+
model_name, dimensions, kind, mode, truncate_dimension,
|
|
7536
8429
|
criteria_json, source_json, compatibility_json, materialization_json, freshness_json, created_at, updated_at
|
|
7537
|
-
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb, $10::jsonb, $
|
|
7538
|
-
ON CONFLICT (id) DO UPDATE SET name = $2,
|
|
7539
|
-
|
|
7540
|
-
|
|
7541
|
-
|
|
8430
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb, $10, $11, $12, $13, $14, $15::jsonb, $16::jsonb, $17::jsonb, $18::jsonb, $19::jsonb, $20, COALESCE($21::timestamptz, $20::timestamptz))
|
|
8431
|
+
ON CONFLICT (id) DO UPDATE SET name = $2, slug = $3, description = $4, purpose = $5,
|
|
8432
|
+
document_count = $6, embedding_count = $7, is_system = $8, keywords_json = $9::jsonb,
|
|
8433
|
+
model_name = $10, dimensions = $11, kind = $12, mode = $13, truncate_dimension = $14,
|
|
8434
|
+
criteria_json = $15::jsonb, source_json = $16::jsonb, compatibility_json = $17::jsonb,
|
|
8435
|
+
materialization_json = $18::jsonb, freshness_json = $19::jsonb, updated_at = COALESCE($21::timestamptz, $20::timestamptz)`,
|
|
8436
|
+
[set.id, set.name, set.slug, set.description, set.purpose, set.document_count, set.embedding_count, set.is_system, set.keywords_json, set.model_name, set.dimensions, set.kind, set.mode, set.truncate_dimension, set.criteria_json, set.source_json, set.compatibility_json, set.materialization_json, set.freshness_json, set.created_at, set.updated_at]
|
|
7542
8437
|
);
|
|
7543
8438
|
} else {
|
|
7544
8439
|
await tx.query(
|
|
7545
8440
|
`INSERT INTO embedding_set (
|
|
7546
|
-
id, name,
|
|
8441
|
+
id, name, slug, description, purpose, document_count, embedding_count, is_system, keywords_json,
|
|
8442
|
+
model_name, dimensions, kind, mode, truncate_dimension,
|
|
7547
8443
|
criteria_json, source_json, compatibility_json, materialization_json, freshness_json, created_at, updated_at
|
|
7548
|
-
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb, $10::jsonb, $
|
|
7549
|
-
[set.id, set.name, set.purpose, set.model_name, set.dimensions, set.kind, set.mode, set.truncate_dimension, set.criteria_json, set.source_json, set.compatibility_json, set.materialization_json, set.freshness_json, set.created_at, set.updated_at]
|
|
8444
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb, $10, $11, $12, $13, $14, $15::jsonb, $16::jsonb, $17::jsonb, $18::jsonb, $19::jsonb, $20, COALESCE($21::timestamptz, $20::timestamptz)) ${conflictClause}`,
|
|
8445
|
+
[set.id, set.name, set.slug, set.description, set.purpose, set.document_count, set.embedding_count, set.is_system, set.keywords_json, set.model_name, set.dimensions, set.kind, set.mode, set.truncate_dimension, set.criteria_json, set.source_json, set.compatibility_json, set.materialization_json, set.freshness_json, set.created_at, set.updated_at]
|
|
7550
8446
|
);
|
|
7551
8447
|
}
|
|
7552
8448
|
counts.embedding_sets++;
|
|
@@ -7556,20 +8452,38 @@ async function importShard(db, data, options) {
|
|
|
7556
8452
|
report?.({ phase: "embeddings", done: 0, total: parsedEmbeddings.length });
|
|
7557
8453
|
for (const [index, shardEmb] of parsedEmbeddings.entries()) {
|
|
7558
8454
|
const emb = embeddingFromShard(shardEmb);
|
|
8455
|
+
const embeddingSetId = emb.embedding_set_id ?? await resolveEmbeddingSetIdForServerEmbedding(tx, emb.model, emb.vector);
|
|
8456
|
+
if (await skipExisting("embeddings", "SELECT 1 FROM embedding WHERE id = $1", [emb.id])) {
|
|
8457
|
+
report?.({ phase: "embeddings", done: index + 1, total: parsedEmbeddings.length });
|
|
8458
|
+
await maybeYield2(index + 1, batchSize);
|
|
8459
|
+
continue;
|
|
8460
|
+
}
|
|
7559
8461
|
if (strategy === "replace") {
|
|
7560
8462
|
await tx.query(
|
|
7561
|
-
`INSERT INTO embedding (id, note_id, embedding_set_id, vector, created_at)
|
|
7562
|
-
VALUES ($1, $2, $3, $4, $5)
|
|
7563
|
-
ON CONFLICT (id) DO UPDATE SET vector = $
|
|
7564
|
-
[emb.id, emb.note_id, emb.
|
|
8463
|
+
`INSERT INTO embedding (id, note_id, embedding_set_id, chunk_index, text, vector, model, created_at)
|
|
8464
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, COALESCE($8::timestamptz, now()))
|
|
8465
|
+
ON CONFLICT (id) DO UPDATE SET embedding_set_id = $3, chunk_index = $4, text = $5, vector = $6, model = $7`,
|
|
8466
|
+
[emb.id, emb.note_id, embeddingSetId, emb.chunk_index, emb.text, emb.vector, emb.model, emb.created_at]
|
|
7565
8467
|
);
|
|
7566
8468
|
} else {
|
|
7567
8469
|
await tx.query(
|
|
7568
|
-
`INSERT INTO embedding (id, note_id, embedding_set_id, vector, created_at)
|
|
7569
|
-
VALUES ($1, $2, $3, $4, $5) ${conflictClause}`,
|
|
7570
|
-
[emb.id, emb.note_id, emb.
|
|
8470
|
+
`INSERT INTO embedding (id, note_id, embedding_set_id, chunk_index, text, vector, model, created_at)
|
|
8471
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, COALESCE($8::timestamptz, now())) ${conflictClause}`,
|
|
8472
|
+
[emb.id, emb.note_id, embeddingSetId, emb.chunk_index, emb.text, emb.vector, emb.model, emb.created_at]
|
|
7571
8473
|
);
|
|
7572
8474
|
}
|
|
8475
|
+
await tx.query(
|
|
8476
|
+
`INSERT INTO embedding_set_member (
|
|
8477
|
+
embedding_set_id, note_id, embedding_id, membership_type, added_at, added_by
|
|
8478
|
+
) VALUES ($1, $2, $3, 'materialized', COALESCE($4::timestamptz, now()), 'shard-import')
|
|
8479
|
+
ON CONFLICT (embedding_set_id, note_id) DO UPDATE SET
|
|
8480
|
+
embedding_id = EXCLUDED.embedding_id,
|
|
8481
|
+
membership_type = CASE
|
|
8482
|
+
WHEN embedding_set_member.membership_type = 'auto' THEN 'materialized'
|
|
8483
|
+
ELSE embedding_set_member.membership_type
|
|
8484
|
+
END`,
|
|
8485
|
+
[embeddingSetId, emb.note_id, emb.id, emb.created_at]
|
|
8486
|
+
);
|
|
7573
8487
|
counts.embeddings++;
|
|
7574
8488
|
report?.({ phase: "embeddings", done: index + 1, total: parsedEmbeddings.length });
|
|
7575
8489
|
await maybeYield2(index + 1, batchSize);
|
|
@@ -7578,6 +8492,11 @@ async function importShard(db, data, options) {
|
|
|
7578
8492
|
let doneGraph = 0;
|
|
7579
8493
|
report?.({ phase: "graph", done: doneGraph, total: totalGraph });
|
|
7580
8494
|
for (const source of parsedGraphSources) {
|
|
8495
|
+
if (await skipExisting("graph_sources", "SELECT 1 FROM graph_source WHERE id = $1", [source.id])) {
|
|
8496
|
+
report?.({ phase: "graph", done: ++doneGraph, total: totalGraph });
|
|
8497
|
+
await maybeYield2(doneGraph, batchSize);
|
|
8498
|
+
continue;
|
|
8499
|
+
}
|
|
7581
8500
|
const parameters = source.parameters == null ? null : JSON.stringify(source.parameters);
|
|
7582
8501
|
const freshness = JSON.stringify({ ...source.freshness ?? {}, status: "unknown" });
|
|
7583
8502
|
await tx.query(
|
|
@@ -7593,6 +8512,15 @@ async function importShard(db, data, options) {
|
|
|
7593
8512
|
await maybeYield2(doneGraph, batchSize);
|
|
7594
8513
|
}
|
|
7595
8514
|
for (const edge of parsedGraphEdges) {
|
|
8515
|
+
if (await skipExisting(
|
|
8516
|
+
"graph_edges",
|
|
8517
|
+
"SELECT 1 FROM graph_edge_artifact WHERE graph_source_id = $1 AND from_note_id = $2 AND to_note_id = $3 AND kind = $4",
|
|
8518
|
+
[edge.graph_source_id, edge.from_note_id, edge.to_note_id, edge.kind]
|
|
8519
|
+
)) {
|
|
8520
|
+
report?.({ phase: "graph", done: ++doneGraph, total: totalGraph });
|
|
8521
|
+
await maybeYield2(doneGraph, batchSize);
|
|
8522
|
+
continue;
|
|
8523
|
+
}
|
|
7596
8524
|
const metadata = edge.metadata == null ? null : JSON.stringify(edge.metadata);
|
|
7597
8525
|
await tx.query(
|
|
7598
8526
|
`INSERT INTO graph_edge_artifact (graph_source_id, from_note_id, to_note_id, weight, kind, rank, metadata_json)
|
|
@@ -7610,16 +8538,22 @@ async function importShard(db, data, options) {
|
|
|
7610
8538
|
for (const set of parsedCommunitySets) {
|
|
7611
8539
|
const parameters = set.parameters == null ? null : JSON.stringify(set.parameters);
|
|
7612
8540
|
const freshness = JSON.stringify({ ...set.freshness ?? {}, status: "unknown" });
|
|
7613
|
-
await
|
|
8541
|
+
const skippedSet = await skipExisting("community_sets", "SELECT 1 FROM community_set WHERE id = $1", [set.id]);
|
|
8542
|
+
if (!skippedSet) await tx.query(
|
|
7614
8543
|
`INSERT INTO community_set (id, graph_source_id, name, source_type, algorithm, parameters_json, input_hash, freshness_json, created_at)
|
|
7615
8544
|
VALUES ($1, $2, $3, $4, $5, $6::jsonb, $7, $8::jsonb, $9)
|
|
7616
8545
|
${strategy === "replace" ? "ON CONFLICT (id) DO UPDATE SET graph_source_id = $2, name = $3, source_type = $4, algorithm = $5, parameters_json = $6::jsonb, input_hash = $7, freshness_json = $8::jsonb, created_at = $9" : conflictClause}`,
|
|
7617
8546
|
[set.id, set.graph_source_id, set.name, set.source_type, set.algorithm ?? null, parameters, set.input_hash, freshness, set.created_at]
|
|
7618
8547
|
);
|
|
7619
|
-
counts.community_sets++;
|
|
8548
|
+
if (!skippedSet) counts.community_sets++;
|
|
7620
8549
|
report?.({ phase: "communities", done: ++doneCommunities, total: totalCommunities });
|
|
7621
8550
|
await maybeYield2(doneCommunities, batchSize);
|
|
7622
8551
|
for (const community of set.communities ?? []) {
|
|
8552
|
+
if (await skipExisting("communities", "SELECT 1 FROM community WHERE community_set_id = $1 AND id = $2", [set.id, community.id])) {
|
|
8553
|
+
report?.({ phase: "communities", done: ++doneCommunities, total: totalCommunities });
|
|
8554
|
+
await maybeYield2(doneCommunities, batchSize);
|
|
8555
|
+
continue;
|
|
8556
|
+
}
|
|
7623
8557
|
const metadata = community.metadata == null ? null : JSON.stringify(community.metadata);
|
|
7624
8558
|
await tx.query(
|
|
7625
8559
|
`INSERT INTO community (community_set_id, id, label, rank, size, confidence, representative_note_ids, metadata_json)
|
|
@@ -7633,6 +8567,11 @@ async function importShard(db, data, options) {
|
|
|
7633
8567
|
}
|
|
7634
8568
|
}
|
|
7635
8569
|
for (const assignment of parsedCommunityAssignments) {
|
|
8570
|
+
if (await skipExisting("community_assignments", "SELECT 1 FROM community_assignment WHERE community_set_id = $1 AND note_id = $2", [assignment.community_set_id, assignment.note_id])) {
|
|
8571
|
+
report?.({ phase: "communities", done: ++doneCommunities, total: totalCommunities });
|
|
8572
|
+
await maybeYield2(doneCommunities, batchSize);
|
|
8573
|
+
continue;
|
|
8574
|
+
}
|
|
7636
8575
|
const metadata = assignment.metadata == null ? null : JSON.stringify(assignment.metadata);
|
|
7637
8576
|
await tx.query(
|
|
7638
8577
|
`INSERT INTO community_assignment (community_set_id, community_id, note_id, confidence, source_type, metadata_json)
|
|
@@ -7646,10 +8585,38 @@ async function importShard(db, data, options) {
|
|
|
7646
8585
|
}
|
|
7647
8586
|
report?.({ phase: "embedding_set_members", done: 0, total: parsedEmbMembers.length });
|
|
7648
8587
|
for (const [index, member] of parsedEmbMembers.entries()) {
|
|
8588
|
+
if (strategy === "skip" && preexistingEmbeddingMembers.has(`${member.embedding_set_id}\0${member.note_id}`)) {
|
|
8589
|
+
skipped.embedding_set_members = (skipped.embedding_set_members ?? 0) + 1;
|
|
8590
|
+
report?.({ phase: "embedding_set_members", done: index + 1, total: parsedEmbMembers.length });
|
|
8591
|
+
await maybeYield2(index + 1, batchSize);
|
|
8592
|
+
continue;
|
|
8593
|
+
}
|
|
8594
|
+
let embeddingId = member.embedding_id ?? null;
|
|
8595
|
+
if (!embeddingId) {
|
|
8596
|
+
const resolvedEmbedding = await tx.query(
|
|
8597
|
+
`SELECT id FROM embedding WHERE embedding_set_id = $1 AND note_id = $2 ORDER BY created_at DESC LIMIT 1`,
|
|
8598
|
+
[member.embedding_set_id, member.note_id]
|
|
8599
|
+
);
|
|
8600
|
+
embeddingId = resolvedEmbedding.rows[0]?.id ?? null;
|
|
8601
|
+
}
|
|
7649
8602
|
await tx.query(
|
|
7650
|
-
`INSERT INTO embedding_set_member (
|
|
7651
|
-
|
|
7652
|
-
|
|
8603
|
+
`INSERT INTO embedding_set_member (
|
|
8604
|
+
embedding_set_id, note_id, embedding_id, membership_type, added_at, added_by
|
|
8605
|
+
)
|
|
8606
|
+
VALUES ($1, $2, $3, $4, $5, $6)
|
|
8607
|
+
ON CONFLICT (embedding_set_id, note_id) DO UPDATE SET
|
|
8608
|
+
embedding_id = COALESCE(EXCLUDED.embedding_id, embedding_set_member.embedding_id),
|
|
8609
|
+
membership_type = EXCLUDED.membership_type,
|
|
8610
|
+
added_at = EXCLUDED.added_at,
|
|
8611
|
+
added_by = EXCLUDED.added_by`,
|
|
8612
|
+
[
|
|
8613
|
+
member.embedding_set_id,
|
|
8614
|
+
member.note_id,
|
|
8615
|
+
embeddingId,
|
|
8616
|
+
member.membership_type ?? "materialized",
|
|
8617
|
+
member.added_at ?? manifest.created_at,
|
|
8618
|
+
member.added_by ?? null
|
|
8619
|
+
]
|
|
7653
8620
|
);
|
|
7654
8621
|
counts.embedding_set_members++;
|
|
7655
8622
|
report?.({ phase: "embedding_set_members", done: index + 1, total: parsedEmbMembers.length });
|
|
@@ -7667,6 +8634,17 @@ async function importShard(db, data, options) {
|
|
|
7667
8634
|
duration_ms: performance.now() - start
|
|
7668
8635
|
};
|
|
7669
8636
|
}
|
|
8637
|
+
if (options?.blobStore && blobsToHydrate.size > 0) {
|
|
8638
|
+
try {
|
|
8639
|
+
for (const [hash, bytes] of blobsToHydrate) {
|
|
8640
|
+
await options.blobStore.write(hash, bytes);
|
|
8641
|
+
}
|
|
8642
|
+
} catch (err) {
|
|
8643
|
+
warnings.push(
|
|
8644
|
+
`Imported metadata successfully but failed to hydrate ${blobsToHydrate.size} attachment blob(s) into the BlobStore: ${err instanceof Error ? err.message : String(err)}.`
|
|
8645
|
+
);
|
|
8646
|
+
}
|
|
8647
|
+
}
|
|
7670
8648
|
return {
|
|
7671
8649
|
success: true,
|
|
7672
8650
|
counts,
|
|
@@ -7676,25 +8654,666 @@ async function importShard(db, data, options) {
|
|
|
7676
8654
|
duration_ms: performance.now() - start
|
|
7677
8655
|
};
|
|
7678
8656
|
}
|
|
7679
|
-
function
|
|
7680
|
-
|
|
7681
|
-
const
|
|
7682
|
-
|
|
8657
|
+
async function resolveEmbeddingSetIdForServerEmbedding(db, model, vector) {
|
|
8658
|
+
const dimension = vectorDimension(vector);
|
|
8659
|
+
const existing = await db.query(
|
|
8660
|
+
`SELECT id FROM embedding_set
|
|
8661
|
+
WHERE model_name = $1 AND dimensions = $2
|
|
8662
|
+
ORDER BY created_at, id
|
|
8663
|
+
LIMIT 1`,
|
|
8664
|
+
[model, dimension]
|
|
8665
|
+
);
|
|
8666
|
+
if (existing.rows[0]?.id) return existing.rows[0].id;
|
|
8667
|
+
const id = generateId();
|
|
8668
|
+
await db.query(
|
|
8669
|
+
`INSERT INTO embedding_set (
|
|
8670
|
+
id, name, slug, description, purpose, document_count, embedding_count,
|
|
8671
|
+
is_system, keywords_json, model_name, dimensions, kind, created_at, updated_at
|
|
8672
|
+
) VALUES ($1, $2, $3, NULL, NULL, 0, 0, false, '[]'::jsonb, $2, $4, 'physical', now(), now())`,
|
|
8673
|
+
[id, model, slugifyServerEmbeddingSet(model), dimension]
|
|
8674
|
+
);
|
|
8675
|
+
return id;
|
|
7683
8676
|
}
|
|
7684
|
-
function
|
|
7685
|
-
|
|
7686
|
-
|
|
8677
|
+
function vectorDimension(vector) {
|
|
8678
|
+
const inner = vector.replace(/^\[/, "").replace(/\]$/, "").trim();
|
|
8679
|
+
if (!inner) return 0;
|
|
8680
|
+
return inner.split(",").length;
|
|
8681
|
+
}
|
|
8682
|
+
function slugifyServerEmbeddingSet(value) {
|
|
8683
|
+
const slug = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
8684
|
+
return slug || "embedding-set";
|
|
7687
8685
|
}
|
|
7688
8686
|
|
|
7689
|
-
//
|
|
7690
|
-
var
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
8687
|
+
// schemas/knowledge-shard.schema.json
|
|
8688
|
+
var knowledge_shard_schema_default = {
|
|
8689
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
8690
|
+
$id: "https://fortemi.dev/schemas/knowledge-shard.schema.json",
|
|
8691
|
+
title: "Knowledge Shard (matric-shard) conformance schema",
|
|
8692
|
+
description: "Structural authority for the Knowledge Shard interchange contract shared between the Fortemi server (GET /api/v1/backup/knowledge-shard) and @fortemi/core. Covers the manifest, server-owned shard components, and React extension components tracked by issue #255.",
|
|
8693
|
+
type: "object",
|
|
8694
|
+
$defs: {
|
|
8695
|
+
isoDateTime: {
|
|
8696
|
+
type: "string",
|
|
8697
|
+
description: "ISO 8601 timestamp",
|
|
8698
|
+
minLength: 1
|
|
8699
|
+
},
|
|
8700
|
+
jsonObject: {
|
|
8701
|
+
type: "object",
|
|
8702
|
+
additionalProperties: true
|
|
8703
|
+
},
|
|
8704
|
+
nullableJsonObject: {
|
|
8705
|
+
type: ["object", "null"],
|
|
8706
|
+
additionalProperties: true
|
|
8707
|
+
},
|
|
8708
|
+
stringArray: {
|
|
8709
|
+
type: "array",
|
|
8710
|
+
items: { type: "string" }
|
|
8711
|
+
},
|
|
8712
|
+
manifest: {
|
|
8713
|
+
type: "object",
|
|
8714
|
+
additionalProperties: false,
|
|
8715
|
+
required: [
|
|
8716
|
+
"version",
|
|
8717
|
+
"matric_version",
|
|
8718
|
+
"format",
|
|
8719
|
+
"created_at",
|
|
8720
|
+
"components",
|
|
8721
|
+
"counts",
|
|
8722
|
+
"checksums",
|
|
8723
|
+
"min_reader_version"
|
|
8724
|
+
],
|
|
8725
|
+
properties: {
|
|
8726
|
+
version: { type: "string", minLength: 1 },
|
|
8727
|
+
matric_version: { type: "string", minLength: 1 },
|
|
8728
|
+
format: { const: "matric-shard" },
|
|
8729
|
+
created_at: { $ref: "#/$defs/isoDateTime" },
|
|
8730
|
+
components: {
|
|
8731
|
+
type: "array",
|
|
8732
|
+
items: {
|
|
8733
|
+
enum: [
|
|
8734
|
+
"notes",
|
|
8735
|
+
"collections",
|
|
8736
|
+
"tags",
|
|
8737
|
+
"templates",
|
|
8738
|
+
"links",
|
|
8739
|
+
"embedding_sets",
|
|
8740
|
+
"embedding_configs",
|
|
8741
|
+
"embedding_set_members",
|
|
8742
|
+
"embeddings",
|
|
8743
|
+
"skos_schemes",
|
|
8744
|
+
"skos_concepts",
|
|
8745
|
+
"skos_relations",
|
|
8746
|
+
"note_skos_tags",
|
|
8747
|
+
"provenance_edges",
|
|
8748
|
+
"community_assignments",
|
|
8749
|
+
"communities",
|
|
8750
|
+
"graph_edges",
|
|
8751
|
+
"graph_sources"
|
|
8752
|
+
]
|
|
8753
|
+
}
|
|
8754
|
+
},
|
|
8755
|
+
counts: {
|
|
8756
|
+
type: "object",
|
|
8757
|
+
additionalProperties: { type: "integer", minimum: 0 }
|
|
8758
|
+
},
|
|
8759
|
+
checksums: {
|
|
8760
|
+
type: "object",
|
|
8761
|
+
description: "filename -> sha256 hex",
|
|
8762
|
+
additionalProperties: { type: "string", pattern: "^[0-9a-f]{64}$" }
|
|
8763
|
+
},
|
|
8764
|
+
min_reader_version: { type: "string", minLength: 1 },
|
|
8765
|
+
migrated_from: {
|
|
8766
|
+
type: ["string", "null"],
|
|
8767
|
+
minLength: 1
|
|
8768
|
+
},
|
|
8769
|
+
migration_history: {
|
|
8770
|
+
type: "array",
|
|
8771
|
+
items: {
|
|
8772
|
+
type: "object",
|
|
8773
|
+
additionalProperties: false,
|
|
8774
|
+
required: [
|
|
8775
|
+
"from_version",
|
|
8776
|
+
"to_version",
|
|
8777
|
+
"migrated_at",
|
|
8778
|
+
"migrated_by",
|
|
8779
|
+
"changes"
|
|
8780
|
+
],
|
|
8781
|
+
properties: {
|
|
8782
|
+
from_version: { type: "string", minLength: 1 },
|
|
8783
|
+
to_version: { type: "string", minLength: 1 },
|
|
8784
|
+
migrated_at: { $ref: "#/$defs/isoDateTime" },
|
|
8785
|
+
migrated_by: { type: "string", minLength: 1 },
|
|
8786
|
+
changes: { $ref: "#/$defs/stringArray" }
|
|
8787
|
+
}
|
|
8788
|
+
}
|
|
8789
|
+
},
|
|
8790
|
+
layout: { type: "object" }
|
|
8791
|
+
}
|
|
8792
|
+
},
|
|
8793
|
+
attachmentReference: {
|
|
8794
|
+
type: "object",
|
|
8795
|
+
additionalProperties: false,
|
|
8796
|
+
required: ["id", "path", "mime", "checksum", "bytes"],
|
|
8797
|
+
properties: {
|
|
8798
|
+
id: { type: "string", minLength: 1 },
|
|
8799
|
+
path: { type: "string", minLength: 1 },
|
|
8800
|
+
mime: { type: ["string", "null"] },
|
|
8801
|
+
checksum: { type: "string", minLength: 1 },
|
|
8802
|
+
bytes: { type: "integer", minimum: 0 }
|
|
8803
|
+
}
|
|
8804
|
+
},
|
|
8805
|
+
attachmentProjection: {
|
|
8806
|
+
type: "object",
|
|
8807
|
+
additionalProperties: false,
|
|
8808
|
+
required: ["extracted_text", "attachment"],
|
|
8809
|
+
properties: {
|
|
8810
|
+
extracted_text: { type: ["string", "null"] },
|
|
8811
|
+
attachment: { $ref: "#/$defs/attachmentReference" }
|
|
8812
|
+
}
|
|
8813
|
+
},
|
|
8814
|
+
note: {
|
|
8815
|
+
type: "object",
|
|
8816
|
+
additionalProperties: false,
|
|
8817
|
+
description: "One row of the shard `notes` component. Field names are the server shard contract, not the PGlite table column names.",
|
|
8818
|
+
required: [
|
|
8819
|
+
"id",
|
|
8820
|
+
"title",
|
|
8821
|
+
"original_content",
|
|
8822
|
+
"revised_content",
|
|
8823
|
+
"format",
|
|
8824
|
+
"source",
|
|
8825
|
+
"starred",
|
|
8826
|
+
"archived",
|
|
8827
|
+
"tags",
|
|
8828
|
+
"created_at",
|
|
8829
|
+
"updated_at"
|
|
8830
|
+
],
|
|
8831
|
+
properties: {
|
|
8832
|
+
id: { type: "string", minLength: 1 },
|
|
8833
|
+
title: { type: ["string", "null"] },
|
|
8834
|
+
original_content: { type: "string" },
|
|
8835
|
+
revised_content: { type: ["string", "null"] },
|
|
8836
|
+
collection_id: { type: ["string", "null"] },
|
|
8837
|
+
attachments: {
|
|
8838
|
+
type: "array",
|
|
8839
|
+
items: { $ref: "#/$defs/attachmentProjection" }
|
|
8840
|
+
},
|
|
8841
|
+
format: { type: "string" },
|
|
8842
|
+
source: { type: "string" },
|
|
8843
|
+
starred: { type: "boolean" },
|
|
8844
|
+
archived: { type: "boolean" },
|
|
8845
|
+
tags: { type: "array", items: { type: "string" } },
|
|
8846
|
+
created_at: { $ref: "#/$defs/isoDateTime" },
|
|
8847
|
+
updated_at: { $ref: "#/$defs/isoDateTime" },
|
|
8848
|
+
deleted_at: { type: ["string", "null"] }
|
|
8849
|
+
}
|
|
8850
|
+
},
|
|
8851
|
+
collection: {
|
|
8852
|
+
type: "object",
|
|
8853
|
+
additionalProperties: false,
|
|
8854
|
+
required: ["id", "name", "description", "parent_id", "created_at"],
|
|
8855
|
+
properties: {
|
|
8856
|
+
id: { type: "string", minLength: 1 },
|
|
8857
|
+
name: { type: "string" },
|
|
8858
|
+
description: { type: ["string", "null"] },
|
|
8859
|
+
parent_id: { type: ["string", "null"] },
|
|
8860
|
+
created_at: { $ref: "#/$defs/isoDateTime" },
|
|
8861
|
+
note_count: { type: "integer", minimum: 0 }
|
|
8862
|
+
}
|
|
8863
|
+
},
|
|
8864
|
+
tag: {
|
|
8865
|
+
type: "object",
|
|
8866
|
+
additionalProperties: false,
|
|
8867
|
+
required: ["name", "created_at"],
|
|
8868
|
+
properties: {
|
|
8869
|
+
name: { type: "string", minLength: 1 },
|
|
8870
|
+
created_at: { $ref: "#/$defs/isoDateTime" }
|
|
8871
|
+
}
|
|
8872
|
+
},
|
|
8873
|
+
template: {
|
|
8874
|
+
type: "object",
|
|
8875
|
+
additionalProperties: false,
|
|
8876
|
+
required: ["id", "name", "description", "content", "format", "default_tags", "collection_id", "created_at", "updated_at"],
|
|
8877
|
+
properties: {
|
|
8878
|
+
id: { type: "string", minLength: 1 },
|
|
8879
|
+
name: { type: "string" },
|
|
8880
|
+
description: { type: ["string", "null"] },
|
|
8881
|
+
content: { type: "string" },
|
|
8882
|
+
format: { type: "string" },
|
|
8883
|
+
default_tags: { $ref: "#/$defs/stringArray" },
|
|
8884
|
+
collection_id: { type: ["string", "null"] },
|
|
8885
|
+
created_at: { $ref: "#/$defs/isoDateTime" },
|
|
8886
|
+
updated_at: { $ref: "#/$defs/isoDateTime" }
|
|
8887
|
+
}
|
|
8888
|
+
},
|
|
8889
|
+
link: {
|
|
8890
|
+
type: "object",
|
|
8891
|
+
additionalProperties: false,
|
|
8892
|
+
required: ["id", "from_note_id", "to_note_id", "to_url", "kind", "score", "created_at", "metadata"],
|
|
8893
|
+
properties: {
|
|
8894
|
+
id: { type: "string", minLength: 1 },
|
|
8895
|
+
from_note_id: { type: "string", minLength: 1 },
|
|
8896
|
+
to_note_id: { type: ["string", "null"] },
|
|
8897
|
+
to_url: { type: ["string", "null"] },
|
|
8898
|
+
kind: { type: "string" },
|
|
8899
|
+
score: { type: ["number", "null"] },
|
|
8900
|
+
created_at: { $ref: "#/$defs/isoDateTime" },
|
|
8901
|
+
metadata: { $ref: "#/$defs/nullableJsonObject" }
|
|
8902
|
+
}
|
|
8903
|
+
},
|
|
8904
|
+
embeddingSet: {
|
|
8905
|
+
type: "object",
|
|
8906
|
+
additionalProperties: false,
|
|
8907
|
+
required: ["id", "name", "slug", "description", "purpose", "document_count", "embedding_count", "is_system", "keywords", "model", "dimension"],
|
|
8908
|
+
properties: {
|
|
8909
|
+
id: { type: "string", minLength: 1 },
|
|
8910
|
+
name: { type: "string" },
|
|
8911
|
+
slug: { type: ["string", "null"] },
|
|
8912
|
+
description: { type: ["string", "null"] },
|
|
8913
|
+
purpose: { type: ["string", "null"] },
|
|
8914
|
+
document_count: { type: "integer", minimum: 0 },
|
|
8915
|
+
embedding_count: { type: "integer", minimum: 0 },
|
|
8916
|
+
is_system: { type: "boolean" },
|
|
8917
|
+
keywords: { $ref: "#/$defs/stringArray" },
|
|
8918
|
+
model: { type: "string" },
|
|
8919
|
+
dimension: { type: "integer", minimum: 1 },
|
|
8920
|
+
kind: { enum: ["physical", "filter", "virtual"] },
|
|
8921
|
+
mode: { type: ["string", "null"] },
|
|
8922
|
+
truncate_dimension: { type: ["integer", "null"], minimum: 1 },
|
|
8923
|
+
criteria: { $ref: "#/$defs/nullableJsonObject" },
|
|
8924
|
+
source: { $ref: "#/$defs/nullableJsonObject" },
|
|
8925
|
+
compatibility: { $ref: "#/$defs/nullableJsonObject" },
|
|
8926
|
+
materialization: { $ref: "#/$defs/nullableJsonObject" },
|
|
8927
|
+
freshness: { $ref: "#/$defs/nullableJsonObject" },
|
|
8928
|
+
created_at: { $ref: "#/$defs/isoDateTime" },
|
|
8929
|
+
updated_at: { $ref: "#/$defs/isoDateTime" }
|
|
8930
|
+
}
|
|
8931
|
+
},
|
|
8932
|
+
embeddingSetMember: {
|
|
8933
|
+
type: "object",
|
|
8934
|
+
additionalProperties: false,
|
|
8935
|
+
required: ["embedding_set_id", "note_id", "membership_type", "added_at", "added_by"],
|
|
8936
|
+
properties: {
|
|
8937
|
+
embedding_set_id: { type: "string", minLength: 1 },
|
|
8938
|
+
note_id: { type: "string", minLength: 1 },
|
|
8939
|
+
membership_type: { type: "string" },
|
|
8940
|
+
added_at: { $ref: "#/$defs/isoDateTime" },
|
|
8941
|
+
added_by: { type: ["string", "null"] }
|
|
8942
|
+
}
|
|
8943
|
+
},
|
|
8944
|
+
embeddingConfig: {
|
|
8945
|
+
type: "object",
|
|
8946
|
+
additionalProperties: false,
|
|
8947
|
+
required: ["id", "name", "description", "model", "dimension", "chunk_size", "chunk_overlap", "is_default"],
|
|
8948
|
+
properties: {
|
|
8949
|
+
id: { type: "string", minLength: 1 },
|
|
8950
|
+
name: { type: "string" },
|
|
8951
|
+
description: { type: ["string", "null"] },
|
|
8952
|
+
model: { type: "string" },
|
|
8953
|
+
dimension: { type: "integer", minimum: 1 },
|
|
8954
|
+
chunk_size: { type: "integer", minimum: 1 },
|
|
8955
|
+
chunk_overlap: { type: "integer", minimum: 0 },
|
|
8956
|
+
is_default: { type: "boolean" }
|
|
8957
|
+
}
|
|
8958
|
+
},
|
|
8959
|
+
embedding: {
|
|
8960
|
+
type: "object",
|
|
8961
|
+
additionalProperties: false,
|
|
8962
|
+
required: ["id", "note_id", "chunk_index", "text", "vector", "model"],
|
|
8963
|
+
properties: {
|
|
8964
|
+
id: { type: "string", minLength: 1 },
|
|
8965
|
+
note_id: { type: "string", minLength: 1 },
|
|
8966
|
+
chunk_index: { type: "integer", minimum: 0 },
|
|
8967
|
+
text: { type: "string" },
|
|
8968
|
+
vector: { type: "array", items: { type: "number" } },
|
|
8969
|
+
model: { type: "string" },
|
|
8970
|
+
embedding_set_id: { type: "string", minLength: 1 },
|
|
8971
|
+
created_at: { $ref: "#/$defs/isoDateTime" }
|
|
8972
|
+
}
|
|
8973
|
+
},
|
|
8974
|
+
skosScheme: {
|
|
8975
|
+
type: "object",
|
|
8976
|
+
additionalProperties: false,
|
|
8977
|
+
required: ["id", "title", "description", "created_at", "updated_at"],
|
|
8978
|
+
properties: {
|
|
8979
|
+
id: { type: "string", minLength: 1 },
|
|
8980
|
+
title: { type: "string" },
|
|
8981
|
+
description: { type: ["string", "null"] },
|
|
8982
|
+
created_at: { $ref: "#/$defs/isoDateTime" },
|
|
8983
|
+
updated_at: { $ref: "#/$defs/isoDateTime" }
|
|
8984
|
+
}
|
|
8985
|
+
},
|
|
8986
|
+
skosConcept: {
|
|
8987
|
+
type: "object",
|
|
8988
|
+
additionalProperties: false,
|
|
8989
|
+
required: ["id", "scheme_id", "pref_label", "alt_labels", "definition", "created_at", "updated_at"],
|
|
8990
|
+
properties: {
|
|
8991
|
+
id: { type: "string", minLength: 1 },
|
|
8992
|
+
scheme_id: { type: "string", minLength: 1 },
|
|
8993
|
+
pref_label: { type: "string" },
|
|
8994
|
+
alt_labels: { $ref: "#/$defs/stringArray" },
|
|
8995
|
+
definition: { type: ["string", "null"] },
|
|
8996
|
+
created_at: { $ref: "#/$defs/isoDateTime" },
|
|
8997
|
+
updated_at: { $ref: "#/$defs/isoDateTime" }
|
|
8998
|
+
}
|
|
8999
|
+
},
|
|
9000
|
+
skosRelation: {
|
|
9001
|
+
type: "object",
|
|
9002
|
+
additionalProperties: false,
|
|
9003
|
+
required: ["id", "source_concept_id", "target_concept_id", "relation_type", "created_at"],
|
|
9004
|
+
properties: {
|
|
9005
|
+
id: { type: "string", minLength: 1 },
|
|
9006
|
+
source_concept_id: { type: "string", minLength: 1 },
|
|
9007
|
+
target_concept_id: { type: "string", minLength: 1 },
|
|
9008
|
+
relation_type: { enum: ["broader", "narrower", "related"] },
|
|
9009
|
+
created_at: { $ref: "#/$defs/isoDateTime" }
|
|
9010
|
+
}
|
|
9011
|
+
},
|
|
9012
|
+
noteSkosTag: {
|
|
9013
|
+
type: "object",
|
|
9014
|
+
additionalProperties: false,
|
|
9015
|
+
required: ["id", "note_id", "concept_id", "created_at"],
|
|
9016
|
+
properties: {
|
|
9017
|
+
id: { type: "string", minLength: 1 },
|
|
9018
|
+
note_id: { type: "string", minLength: 1 },
|
|
9019
|
+
concept_id: { type: "string", minLength: 1 },
|
|
9020
|
+
created_at: { $ref: "#/$defs/isoDateTime" }
|
|
9021
|
+
}
|
|
9022
|
+
},
|
|
9023
|
+
provenanceEdge: {
|
|
9024
|
+
type: "object",
|
|
9025
|
+
additionalProperties: false,
|
|
9026
|
+
required: ["id", "entity_type", "entity_id", "activity", "agent", "started_at", "ended_at", "attributes"],
|
|
9027
|
+
properties: {
|
|
9028
|
+
id: { type: "string", minLength: 1 },
|
|
9029
|
+
entity_type: { type: "string" },
|
|
9030
|
+
entity_id: { type: "string", minLength: 1 },
|
|
9031
|
+
activity: { type: "string" },
|
|
9032
|
+
agent: { type: "string" },
|
|
9033
|
+
started_at: { $ref: "#/$defs/isoDateTime" },
|
|
9034
|
+
ended_at: { type: ["string", "null"] },
|
|
9035
|
+
attributes: { $ref: "#/$defs/nullableJsonObject" }
|
|
9036
|
+
}
|
|
9037
|
+
},
|
|
9038
|
+
artifactFreshness: {
|
|
9039
|
+
type: "object",
|
|
9040
|
+
additionalProperties: false,
|
|
9041
|
+
required: ["status"],
|
|
9042
|
+
properties: {
|
|
9043
|
+
status: { enum: ["fresh", "stale", "unknown"] },
|
|
9044
|
+
checked_at: { $ref: "#/$defs/isoDateTime" },
|
|
9045
|
+
stale_reason: { type: "string" },
|
|
9046
|
+
source_hashes: { $ref: "#/$defs/jsonObject" }
|
|
9047
|
+
}
|
|
9048
|
+
},
|
|
9049
|
+
graphSource: {
|
|
9050
|
+
type: "object",
|
|
9051
|
+
additionalProperties: false,
|
|
9052
|
+
required: ["id", "name", "kind", "input_hash", "freshness", "created_at"],
|
|
9053
|
+
properties: {
|
|
9054
|
+
id: { type: "string", minLength: 1 },
|
|
9055
|
+
name: { type: "string" },
|
|
9056
|
+
kind: { enum: ["link", "similarity", "search", "manual", "imported"] },
|
|
9057
|
+
source_table: { type: ["string", "null"] },
|
|
9058
|
+
embedding_set_id: { type: ["string", "null"] },
|
|
9059
|
+
virtual_set_id: { type: ["string", "null"] },
|
|
9060
|
+
model: { type: ["string", "null"] },
|
|
9061
|
+
dimension: { type: ["integer", "null"], minimum: 1 },
|
|
9062
|
+
truncate_dimension: { type: ["integer", "null"], minimum: 1 },
|
|
9063
|
+
metric: { type: ["string", "null"] },
|
|
9064
|
+
algorithm: { type: ["string", "null"] },
|
|
9065
|
+
parameters: { $ref: "#/$defs/jsonObject" },
|
|
9066
|
+
input_hash: { type: "string" },
|
|
9067
|
+
freshness: { $ref: "#/$defs/artifactFreshness" },
|
|
9068
|
+
created_at: { $ref: "#/$defs/isoDateTime" }
|
|
9069
|
+
}
|
|
9070
|
+
},
|
|
9071
|
+
graphEdge: {
|
|
9072
|
+
type: "object",
|
|
9073
|
+
additionalProperties: false,
|
|
9074
|
+
required: ["graph_source_id", "from_note_id", "to_note_id", "weight", "kind"],
|
|
9075
|
+
properties: {
|
|
9076
|
+
graph_source_id: { type: "string", minLength: 1 },
|
|
9077
|
+
from_note_id: { type: "string", minLength: 1 },
|
|
9078
|
+
to_note_id: { type: "string", minLength: 1 },
|
|
9079
|
+
weight: { type: "number" },
|
|
9080
|
+
kind: { enum: ["link", "similarity", "manual"] },
|
|
9081
|
+
rank: { type: ["integer", "null"], minimum: 0 },
|
|
9082
|
+
metadata: { $ref: "#/$defs/jsonObject" }
|
|
9083
|
+
}
|
|
9084
|
+
},
|
|
9085
|
+
community: {
|
|
9086
|
+
type: "object",
|
|
9087
|
+
additionalProperties: false,
|
|
9088
|
+
required: ["id"],
|
|
9089
|
+
properties: {
|
|
9090
|
+
id: { type: "string", minLength: 1 },
|
|
9091
|
+
label: { type: ["string", "null"] },
|
|
9092
|
+
rank: { type: ["integer", "null"], minimum: 0 },
|
|
9093
|
+
size: { type: ["integer", "null"], minimum: 0 },
|
|
9094
|
+
confidence: { type: ["number", "null"] },
|
|
9095
|
+
representative_note_ids: { $ref: "#/$defs/stringArray" },
|
|
9096
|
+
metadata: { $ref: "#/$defs/jsonObject" }
|
|
9097
|
+
}
|
|
9098
|
+
},
|
|
9099
|
+
communitySet: {
|
|
9100
|
+
type: "object",
|
|
9101
|
+
additionalProperties: false,
|
|
9102
|
+
required: ["id", "graph_source_id", "name", "source_type", "input_hash", "freshness", "communities", "created_at"],
|
|
9103
|
+
properties: {
|
|
9104
|
+
id: { type: "string", minLength: 1 },
|
|
9105
|
+
graph_source_id: { type: "string", minLength: 1 },
|
|
9106
|
+
name: { type: "string" },
|
|
9107
|
+
source_type: { enum: ["precomputed", "dynamic-snapshot", "user-authored", "imported"] },
|
|
9108
|
+
algorithm: { type: ["string", "null"] },
|
|
9109
|
+
parameters: { $ref: "#/$defs/jsonObject" },
|
|
9110
|
+
input_hash: { type: "string" },
|
|
9111
|
+
freshness: { $ref: "#/$defs/artifactFreshness" },
|
|
9112
|
+
communities: {
|
|
9113
|
+
type: "array",
|
|
9114
|
+
items: { $ref: "#/$defs/community" }
|
|
9115
|
+
},
|
|
9116
|
+
created_at: { $ref: "#/$defs/isoDateTime" }
|
|
9117
|
+
}
|
|
9118
|
+
},
|
|
9119
|
+
communityAssignment: {
|
|
9120
|
+
type: "object",
|
|
9121
|
+
additionalProperties: false,
|
|
9122
|
+
required: ["community_set_id", "community_id", "note_id", "source_type"],
|
|
9123
|
+
properties: {
|
|
9124
|
+
community_set_id: { type: "string", minLength: 1 },
|
|
9125
|
+
community_id: { type: "string", minLength: 1 },
|
|
9126
|
+
note_id: { type: "string", minLength: 1 },
|
|
9127
|
+
confidence: { type: ["number", "null"] },
|
|
9128
|
+
source_type: { enum: ["precomputed", "dynamic-snapshot", "user-authored", "imported"] },
|
|
9129
|
+
metadata: { $ref: "#/$defs/jsonObject" }
|
|
9130
|
+
}
|
|
9131
|
+
}
|
|
9132
|
+
}
|
|
9133
|
+
};
|
|
9134
|
+
|
|
9135
|
+
// src/shard/schema-validator.ts
|
|
9136
|
+
var decoder3 = new TextDecoder();
|
|
9137
|
+
var COMPONENT_SCHEMA_DEFS = {
|
|
9138
|
+
notes: "note",
|
|
9139
|
+
collections: "collection",
|
|
9140
|
+
tags: "tag",
|
|
9141
|
+
templates: "template",
|
|
9142
|
+
links: "link",
|
|
9143
|
+
embedding_sets: "embeddingSet",
|
|
9144
|
+
embedding_set_members: "embeddingSetMember",
|
|
9145
|
+
embedding_configs: "embeddingConfig",
|
|
9146
|
+
embeddings: "embedding",
|
|
9147
|
+
skos_schemes: "skosScheme",
|
|
9148
|
+
skos_concepts: "skosConcept",
|
|
9149
|
+
skos_relations: "skosRelation",
|
|
9150
|
+
note_skos_tags: "noteSkosTag",
|
|
9151
|
+
provenance_edges: "provenanceEdge",
|
|
9152
|
+
graph_sources: "graphSource",
|
|
9153
|
+
graph_edges: "graphEdge",
|
|
9154
|
+
communities: "communitySet",
|
|
9155
|
+
community_assignments: "communityAssignment"
|
|
9156
|
+
};
|
|
9157
|
+
var COMPONENT_FILES = {
|
|
9158
|
+
notes: { file: "notes.jsonl", encoding: "jsonl" },
|
|
9159
|
+
collections: { file: "collections.json", encoding: "json-array" },
|
|
9160
|
+
tags: { file: "tags.json", encoding: "json-array" },
|
|
9161
|
+
templates: { file: "templates.json", encoding: "json-array" },
|
|
9162
|
+
links: { file: "links.jsonl", encoding: "jsonl" },
|
|
9163
|
+
embedding_sets: { file: "embedding_sets.json", encoding: "json-array" },
|
|
9164
|
+
embedding_set_members: { file: "embedding_set_members.jsonl", encoding: "jsonl" },
|
|
9165
|
+
embedding_configs: { file: "embedding_configs.json", encoding: "json-array" },
|
|
9166
|
+
embeddings: { file: "embeddings.jsonl", encoding: "jsonl" },
|
|
9167
|
+
skos_schemes: { file: "skos_schemes.json", encoding: "json-array" },
|
|
9168
|
+
skos_concepts: { file: "skos_concepts.json", encoding: "json-array" },
|
|
9169
|
+
skos_relations: { file: "skos_relations.jsonl", encoding: "jsonl" },
|
|
9170
|
+
note_skos_tags: { file: "note_skos_tags.jsonl", encoding: "jsonl" },
|
|
9171
|
+
provenance_edges: { file: "provenance_edges.jsonl", encoding: "jsonl" },
|
|
9172
|
+
graph_sources: { file: "graph_sources.json", encoding: "json-array" },
|
|
9173
|
+
graph_edges: { file: "graph_edges.jsonl", encoding: "jsonl" },
|
|
9174
|
+
communities: { file: "communities.json", encoding: "json-array" },
|
|
9175
|
+
community_assignments: { file: "community_assignments.jsonl", encoding: "jsonl" }
|
|
9176
|
+
};
|
|
9177
|
+
var ajvInstance;
|
|
9178
|
+
var validators = /* @__PURE__ */ new Map();
|
|
9179
|
+
function getKnowledgeShardSchema() {
|
|
9180
|
+
return knowledge_shard_schema_default;
|
|
9181
|
+
}
|
|
9182
|
+
function getAjv() {
|
|
9183
|
+
if (!ajvInstance) {
|
|
9184
|
+
ajvInstance = new Ajv2020({
|
|
9185
|
+
allErrors: true,
|
|
9186
|
+
strict: true,
|
|
9187
|
+
validateFormats: false
|
|
9188
|
+
});
|
|
9189
|
+
ajvInstance.addSchema(knowledge_shard_schema_default);
|
|
9190
|
+
}
|
|
9191
|
+
return ajvInstance;
|
|
7694
9192
|
}
|
|
7695
|
-
function
|
|
7696
|
-
|
|
7697
|
-
|
|
9193
|
+
function validatorFor(defName) {
|
|
9194
|
+
const cached = validators.get(defName);
|
|
9195
|
+
if (cached) return cached;
|
|
9196
|
+
const validator = getAjv().getSchema(`${knowledge_shard_schema_default.$id}#/$defs/${defName}`) ?? getAjv().compile({ $ref: `${knowledge_shard_schema_default.$id}#/$defs/${defName}` });
|
|
9197
|
+
validators.set(defName, validator);
|
|
9198
|
+
return validator;
|
|
9199
|
+
}
|
|
9200
|
+
function formatErrors(errors) {
|
|
9201
|
+
return (errors ?? []).map((error) => {
|
|
9202
|
+
const path = error.instancePath || "(root)";
|
|
9203
|
+
return `${path} ${error.message ?? "is invalid"}`;
|
|
9204
|
+
});
|
|
9205
|
+
}
|
|
9206
|
+
function validateShardManifest(value) {
|
|
9207
|
+
const validate = validatorFor("manifest");
|
|
9208
|
+
const valid = validate(value);
|
|
9209
|
+
return { valid, errors: formatErrors(validate.errors) };
|
|
9210
|
+
}
|
|
9211
|
+
function parseJsonArray(bytes, path) {
|
|
9212
|
+
if (!bytes) return { records: [], errors: [] };
|
|
9213
|
+
try {
|
|
9214
|
+
const value = JSON.parse(decoder3.decode(bytes));
|
|
9215
|
+
if (!Array.isArray(value)) return { records: [], errors: [`${path} must be a JSON array`] };
|
|
9216
|
+
return { records: value, errors: [] };
|
|
9217
|
+
} catch (error) {
|
|
9218
|
+
return { records: [], errors: [`${path} failed to parse: ${error instanceof Error ? error.message : String(error)}`] };
|
|
9219
|
+
}
|
|
9220
|
+
}
|
|
9221
|
+
function parseJsonl(bytes, path) {
|
|
9222
|
+
if (!bytes) return { records: [], errors: [] };
|
|
9223
|
+
const text = decoder3.decode(bytes).trim();
|
|
9224
|
+
if (!text) return { records: [], errors: [] };
|
|
9225
|
+
const records = [];
|
|
9226
|
+
const errors = [];
|
|
9227
|
+
for (const [index, line] of text.split("\n").entries()) {
|
|
9228
|
+
try {
|
|
9229
|
+
records.push(JSON.parse(line));
|
|
9230
|
+
} catch (error) {
|
|
9231
|
+
errors.push(`${path}:${index + 1} failed to parse: ${error instanceof Error ? error.message : String(error)}`);
|
|
9232
|
+
}
|
|
9233
|
+
}
|
|
9234
|
+
return { records, errors };
|
|
9235
|
+
}
|
|
9236
|
+
function unpackShardFiles(input) {
|
|
9237
|
+
if (input instanceof Map) return input;
|
|
9238
|
+
return unpackTarGz(input instanceof ArrayBuffer ? new Uint8Array(input) : input);
|
|
9239
|
+
}
|
|
9240
|
+
function validateComponentRecords(component, records, path) {
|
|
9241
|
+
const errors = [];
|
|
9242
|
+
for (const [index, record] of records.entries()) {
|
|
9243
|
+
const result = validateShardComponentRecord(component, record);
|
|
9244
|
+
if (!result.valid) {
|
|
9245
|
+
errors.push(...result.errors.map((error) => `${path}[${index}] ${error}`));
|
|
9246
|
+
}
|
|
9247
|
+
}
|
|
9248
|
+
return errors;
|
|
9249
|
+
}
|
|
9250
|
+
function validateShardArchive(input) {
|
|
9251
|
+
let files;
|
|
9252
|
+
try {
|
|
9253
|
+
files = unpackShardFiles(input);
|
|
9254
|
+
} catch (error) {
|
|
9255
|
+
return {
|
|
9256
|
+
valid: false,
|
|
9257
|
+
errors: [`archive failed to unpack: ${error instanceof Error ? error.message : String(error)}`]
|
|
9258
|
+
};
|
|
9259
|
+
}
|
|
9260
|
+
const manifestBytes = files.get("manifest.json");
|
|
9261
|
+
if (!manifestBytes) return { valid: false, errors: ["manifest.json is missing"] };
|
|
9262
|
+
let manifest;
|
|
9263
|
+
try {
|
|
9264
|
+
manifest = JSON.parse(decoder3.decode(manifestBytes));
|
|
9265
|
+
} catch (error) {
|
|
9266
|
+
return {
|
|
9267
|
+
valid: false,
|
|
9268
|
+
errors: [`manifest.json failed to parse: ${error instanceof Error ? error.message : String(error)}`]
|
|
9269
|
+
};
|
|
9270
|
+
}
|
|
9271
|
+
const errors = [];
|
|
9272
|
+
const manifestResult = validateShardManifest(manifest);
|
|
9273
|
+
if (!manifestResult.valid) {
|
|
9274
|
+
errors.push(...manifestResult.errors.map((error) => `manifest.json ${error}`));
|
|
9275
|
+
}
|
|
9276
|
+
const componentsToValidate = new Set(manifest.components);
|
|
9277
|
+
for (const [component, spec] of Object.entries(COMPONENT_FILES)) {
|
|
9278
|
+
if (files.has(spec.file)) componentsToValidate.add(component);
|
|
9279
|
+
}
|
|
9280
|
+
for (const component of componentsToValidate) {
|
|
9281
|
+
const spec = COMPONENT_FILES[component];
|
|
9282
|
+
if (!spec) continue;
|
|
9283
|
+
if (component === "notes" && manifest.layout?.clusters?.notes?.length) {
|
|
9284
|
+
for (const cluster of manifest.layout.clusters.notes) {
|
|
9285
|
+
const parsed2 = parseJsonl(files.get(cluster.href), cluster.href);
|
|
9286
|
+
errors.push(...parsed2.errors);
|
|
9287
|
+
errors.push(...validateComponentRecords("notes", parsed2.records, cluster.href));
|
|
9288
|
+
}
|
|
9289
|
+
continue;
|
|
9290
|
+
}
|
|
9291
|
+
const parsed = spec.encoding === "json-array" ? parseJsonArray(files.get(spec.file), spec.file) : parseJsonl(files.get(spec.file), spec.file);
|
|
9292
|
+
errors.push(...parsed.errors);
|
|
9293
|
+
errors.push(...validateComponentRecords(component, parsed.records, spec.file));
|
|
9294
|
+
}
|
|
9295
|
+
return { valid: errors.length === 0, errors };
|
|
9296
|
+
}
|
|
9297
|
+
function validateShardComponentRecord(component, value) {
|
|
9298
|
+
const defName = COMPONENT_SCHEMA_DEFS[component];
|
|
9299
|
+
const validate = validatorFor(defName);
|
|
9300
|
+
const valid = validate(value);
|
|
9301
|
+
return { valid, errors: formatErrors(validate.errors) };
|
|
9302
|
+
}
|
|
9303
|
+
function assertShardComponentRecord(component, value) {
|
|
9304
|
+
const result = validateShardComponentRecord(component, value);
|
|
9305
|
+
if (!result.valid) {
|
|
9306
|
+
throw new Error(`Invalid shard ${component} record:
|
|
9307
|
+
${result.errors.join("\n")}`);
|
|
9308
|
+
}
|
|
9309
|
+
}
|
|
9310
|
+
|
|
9311
|
+
// src/shard/shard-reader.ts
|
|
9312
|
+
var decoder4 = new TextDecoder();
|
|
9313
|
+
function assertSafeComponentName(filename) {
|
|
9314
|
+
if (filename.length === 0 || filename.startsWith("/") || filename.includes("\\") || filename.includes("\0") || filename.includes(":") || filename.split("/").some((segment) => segment === "..")) {
|
|
9315
|
+
throw new Error(`Refusing to read unsafe shard component path: ${JSON.stringify(filename)}`);
|
|
9316
|
+
}
|
|
7698
9317
|
}
|
|
7699
9318
|
var DEFAULT_WEIGHTS = { title: 4, content: 1, tag: 2 };
|
|
7700
9319
|
async function toBytes(blobOrBytes) {
|
|
@@ -7708,8 +9327,13 @@ var PackedComponentStore = class {
|
|
|
7708
9327
|
this.files = files;
|
|
7709
9328
|
this.manifest = manifest;
|
|
7710
9329
|
}
|
|
7711
|
-
read(filename) {
|
|
7712
|
-
|
|
9330
|
+
async read(filename) {
|
|
9331
|
+
const bytes = this.files.get(filename);
|
|
9332
|
+
const expectedChecksum = this.manifest.checksums?.[filename];
|
|
9333
|
+
if (bytes && expectedChecksum && await sha256Hex(bytes) !== expectedChecksum) {
|
|
9334
|
+
throw new Error(`Checksum validation failed for shard component: ${filename}`);
|
|
9335
|
+
}
|
|
9336
|
+
return bytes;
|
|
7713
9337
|
}
|
|
7714
9338
|
};
|
|
7715
9339
|
var UrlComponentStore = class {
|
|
@@ -7717,24 +9341,62 @@ var UrlComponentStore = class {
|
|
|
7717
9341
|
baseUrl;
|
|
7718
9342
|
fetchImpl;
|
|
7719
9343
|
cache = /* @__PURE__ */ new Map();
|
|
7720
|
-
|
|
9344
|
+
maxComponentBytes;
|
|
9345
|
+
constructor(baseUrl, fetchImpl, manifest, maxComponentBytes) {
|
|
7721
9346
|
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
7722
9347
|
this.fetchImpl = fetchImpl;
|
|
7723
9348
|
this.manifest = manifest;
|
|
9349
|
+
this.maxComponentBytes = maxComponentBytes;
|
|
7724
9350
|
}
|
|
7725
9351
|
async read(filename) {
|
|
9352
|
+
assertSafeComponentName(filename);
|
|
7726
9353
|
if (this.cache.has(filename)) return this.cache.get(filename);
|
|
7727
9354
|
const response = await this.fetchImpl(`${this.baseUrl}/${filename}`);
|
|
7728
9355
|
if (!response.ok) {
|
|
7729
9356
|
this.cache.set(filename, void 0);
|
|
7730
9357
|
return void 0;
|
|
7731
9358
|
}
|
|
7732
|
-
const
|
|
9359
|
+
const declaredLength = Number(response.headers.get("content-length"));
|
|
9360
|
+
if (Number.isFinite(declaredLength) && declaredLength > this.maxComponentBytes) {
|
|
9361
|
+
throw new Error(`Shard component ${filename} exceeds cap ${this.maxComponentBytes} bytes`);
|
|
9362
|
+
}
|
|
9363
|
+
const chunks = [];
|
|
9364
|
+
let total = 0;
|
|
9365
|
+
if (response.body) {
|
|
9366
|
+
const reader = response.body.getReader();
|
|
9367
|
+
while (true) {
|
|
9368
|
+
const { done, value } = await reader.read();
|
|
9369
|
+
if (done) break;
|
|
9370
|
+
total += value.byteLength;
|
|
9371
|
+
if (total > this.maxComponentBytes) {
|
|
9372
|
+
await reader.cancel();
|
|
9373
|
+
throw new Error(`Shard component ${filename} exceeds cap ${this.maxComponentBytes} bytes`);
|
|
9374
|
+
}
|
|
9375
|
+
chunks.push(value);
|
|
9376
|
+
}
|
|
9377
|
+
} else {
|
|
9378
|
+
const value = new Uint8Array(await response.arrayBuffer());
|
|
9379
|
+
total = value.byteLength;
|
|
9380
|
+
if (total > this.maxComponentBytes) {
|
|
9381
|
+
throw new Error(`Shard component ${filename} exceeds cap ${this.maxComponentBytes} bytes`);
|
|
9382
|
+
}
|
|
9383
|
+
chunks.push(value);
|
|
9384
|
+
}
|
|
9385
|
+
const bytes = new Uint8Array(total);
|
|
9386
|
+
let offset = 0;
|
|
9387
|
+
for (const chunk of chunks) {
|
|
9388
|
+
bytes.set(chunk, offset);
|
|
9389
|
+
offset += chunk.byteLength;
|
|
9390
|
+
}
|
|
9391
|
+
const expectedChecksum = this.manifest.checksums?.[filename];
|
|
9392
|
+
if (expectedChecksum && await sha256Hex(bytes) !== expectedChecksum) {
|
|
9393
|
+
throw new Error(`Checksum validation failed for shard component: ${filename}`);
|
|
9394
|
+
}
|
|
7733
9395
|
this.cache.set(filename, bytes);
|
|
7734
9396
|
return bytes;
|
|
7735
9397
|
}
|
|
7736
9398
|
};
|
|
7737
|
-
async function resolveStore(source) {
|
|
9399
|
+
async function resolveStore(source, maxComponentBytes) {
|
|
7738
9400
|
if (typeof source === "object" && "baseUrl" in source) {
|
|
7739
9401
|
const fetchImpl = source.fetchImpl ?? globalThis.fetch;
|
|
7740
9402
|
const base = source.baseUrl.replace(/\/$/, "");
|
|
@@ -7743,20 +9405,20 @@ async function resolveStore(source) {
|
|
|
7743
9405
|
throw new Error(`Failed to fetch shard manifest (${manifestResponse.status}): ${base}/manifest.json`);
|
|
7744
9406
|
}
|
|
7745
9407
|
const manifest2 = await manifestResponse.json();
|
|
7746
|
-
return new UrlComponentStore(base, fetchImpl, manifest2);
|
|
9408
|
+
return new UrlComponentStore(base, fetchImpl, manifest2, maxComponentBytes);
|
|
7747
9409
|
}
|
|
7748
9410
|
const bytes = await toBytes(source);
|
|
7749
9411
|
const files = unpackTarGz(bytes);
|
|
7750
9412
|
const manifestBytes = files.get("manifest.json");
|
|
7751
9413
|
if (!manifestBytes) throw new Error("Missing manifest.json in shard archive");
|
|
7752
|
-
const manifest = JSON.parse(
|
|
9414
|
+
const manifest = JSON.parse(decoder4.decode(manifestBytes));
|
|
7753
9415
|
return new PackedComponentStore(files, manifest);
|
|
7754
9416
|
}
|
|
7755
9417
|
function tokenize(query) {
|
|
7756
9418
|
return query.toLowerCase().split(/[^a-z0-9]+/i).filter((token) => token.length > 0);
|
|
7757
9419
|
}
|
|
7758
9420
|
function noteSearchText(note) {
|
|
7759
|
-
const extractedText = note.binary_sources?.map((source) => source.extracted_text).filter(Boolean).join(" ") ?? "";
|
|
9421
|
+
const extractedText = (note.attachments ?? note.binary_sources)?.map((source) => source.extracted_text).filter(Boolean).join(" ") ?? "";
|
|
7760
9422
|
return `${note.title ?? ""} ${note.original_content} ${note.revised_content ?? ""} ${extractedText}`.toLowerCase();
|
|
7761
9423
|
}
|
|
7762
9424
|
function countOccurrences(haystack, needle) {
|
|
@@ -7778,7 +9440,7 @@ function noteMatchesTokens(note, tokens) {
|
|
|
7778
9440
|
function rankNote(note, tokens, weights) {
|
|
7779
9441
|
if (tokens.length === 0) return 0;
|
|
7780
9442
|
const title = (note.title ?? "").toLowerCase();
|
|
7781
|
-
const extractedText = note.binary_sources?.map((source) => source.extracted_text).filter(Boolean).join(" ") ?? "";
|
|
9443
|
+
const extractedText = (note.attachments ?? note.binary_sources)?.map((source) => source.extracted_text).filter(Boolean).join(" ") ?? "";
|
|
7782
9444
|
const content = `${note.original_content} ${note.revised_content ?? ""} ${extractedText}`.toLowerCase();
|
|
7783
9445
|
const tagText = note.tags.join(" ").toLowerCase();
|
|
7784
9446
|
let score = 0;
|
|
@@ -7790,7 +9452,7 @@ function rankNote(note, tokens, weights) {
|
|
|
7790
9452
|
return score;
|
|
7791
9453
|
}
|
|
7792
9454
|
function makeSnippet(note, tokens, length) {
|
|
7793
|
-
const extractedText = note.binary_sources?.map((source) => source.extracted_text).filter(Boolean).join(" ") ?? "";
|
|
9455
|
+
const extractedText = (note.attachments ?? note.binary_sources)?.map((source) => source.extracted_text).filter(Boolean).join(" ") ?? "";
|
|
7794
9456
|
const content = `${note.original_content} ${note.revised_content ?? ""} ${extractedText}`.trim();
|
|
7795
9457
|
if (tokens.length === 0) return content.slice(0, length);
|
|
7796
9458
|
const lower = content.toLowerCase();
|
|
@@ -8024,8 +9686,8 @@ var ShardReaderImpl = class {
|
|
|
8024
9686
|
}
|
|
8025
9687
|
};
|
|
8026
9688
|
async function openShard(source, options = {}) {
|
|
8027
|
-
const store = await resolveStore(source);
|
|
8028
|
-
if (store.manifest.min_reader_version && store.manifest.min_reader_version >
|
|
9689
|
+
const store = await resolveStore(source, options.maxComponentBytes ?? DEFAULT_MAX_DECOMPRESSED_BYTES);
|
|
9690
|
+
if (store.manifest.min_reader_version && compareShardVersions(store.manifest.min_reader_version, CURRENT_SHARD_VERSION) > 0) {
|
|
8029
9691
|
throw new Error(
|
|
8030
9692
|
`Shard requires reader version ${store.manifest.min_reader_version}, but this build supports ${CURRENT_SHARD_VERSION}. Import the shard instead.`
|
|
8031
9693
|
);
|
|
@@ -8034,7 +9696,7 @@ async function openShard(source, options = {}) {
|
|
|
8034
9696
|
}
|
|
8035
9697
|
|
|
8036
9698
|
// src/shard/semantic-providers.ts
|
|
8037
|
-
var
|
|
9699
|
+
var decoder5 = new TextDecoder();
|
|
8038
9700
|
function cosine(a, b) {
|
|
8039
9701
|
let dot = 0;
|
|
8040
9702
|
let normA = 0;
|
|
@@ -8058,7 +9720,7 @@ function createCosineSemanticProvider(options) {
|
|
|
8058
9720
|
vectors = [];
|
|
8059
9721
|
return;
|
|
8060
9722
|
}
|
|
8061
|
-
vectors =
|
|
9723
|
+
vectors = decoder5.decode(bytes).split("\n").filter((line) => line.trim().length > 0).map((line) => JSON.parse(line));
|
|
8062
9724
|
},
|
|
8063
9725
|
async search(query, k) {
|
|
8064
9726
|
const queryVector = await options.embedQuery(query);
|
|
@@ -8182,6 +9844,723 @@ function clearPrefetchedShard(url) {
|
|
|
8182
9844
|
warmStore.delete(url);
|
|
8183
9845
|
}
|
|
8184
9846
|
|
|
9847
|
+
// schemas/aiwg-fortemi-index-export.schema.json
|
|
9848
|
+
var aiwg_fortemi_index_export_schema_default = {
|
|
9849
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
9850
|
+
$id: "https://aiwg.io/schemas/aiwg-fortemi-index-export.json",
|
|
9851
|
+
title: "AIWG Fortemi Index Export",
|
|
9852
|
+
type: "object",
|
|
9853
|
+
additionalProperties: false,
|
|
9854
|
+
required: ["schema_version", "generated_at", "source", "items"],
|
|
9855
|
+
properties: {
|
|
9856
|
+
schema_version: {
|
|
9857
|
+
enum: ["aiwg.fortemi.index.export.v1", "aiwg.fortemi.index.export.v2"]
|
|
9858
|
+
},
|
|
9859
|
+
generated_at: {
|
|
9860
|
+
type: "string",
|
|
9861
|
+
format: "date-time"
|
|
9862
|
+
},
|
|
9863
|
+
source: {
|
|
9864
|
+
type: "object",
|
|
9865
|
+
additionalProperties: false,
|
|
9866
|
+
required: ["repo", "privacy"],
|
|
9867
|
+
properties: {
|
|
9868
|
+
repo: {
|
|
9869
|
+
type: "string",
|
|
9870
|
+
minLength: 1
|
|
9871
|
+
},
|
|
9872
|
+
privacy: {
|
|
9873
|
+
$ref: "#/$defs/privacy"
|
|
9874
|
+
},
|
|
9875
|
+
graph: {
|
|
9876
|
+
type: "string",
|
|
9877
|
+
minLength: 1
|
|
9878
|
+
}
|
|
9879
|
+
}
|
|
9880
|
+
},
|
|
9881
|
+
compatibility: {
|
|
9882
|
+
type: "object",
|
|
9883
|
+
additionalProperties: false,
|
|
9884
|
+
required: ["previous_schema_version", "strategy"],
|
|
9885
|
+
properties: {
|
|
9886
|
+
previous_schema_version: {
|
|
9887
|
+
const: "aiwg.fortemi.index.export.v1"
|
|
9888
|
+
},
|
|
9889
|
+
strategy: {
|
|
9890
|
+
const: "supported"
|
|
9891
|
+
}
|
|
9892
|
+
}
|
|
9893
|
+
},
|
|
9894
|
+
items: {
|
|
9895
|
+
type: "array",
|
|
9896
|
+
items: {
|
|
9897
|
+
$ref: "#/$defs/record"
|
|
9898
|
+
}
|
|
9899
|
+
}
|
|
9900
|
+
},
|
|
9901
|
+
allOf: [
|
|
9902
|
+
{
|
|
9903
|
+
if: {
|
|
9904
|
+
properties: {
|
|
9905
|
+
schema_version: {
|
|
9906
|
+
const: "aiwg.fortemi.index.export.v1"
|
|
9907
|
+
}
|
|
9908
|
+
}
|
|
9909
|
+
},
|
|
9910
|
+
then: {
|
|
9911
|
+
not: {
|
|
9912
|
+
required: ["compatibility"]
|
|
9913
|
+
},
|
|
9914
|
+
properties: {
|
|
9915
|
+
source: {
|
|
9916
|
+
not: {
|
|
9917
|
+
required: ["graph"]
|
|
9918
|
+
}
|
|
9919
|
+
},
|
|
9920
|
+
items: {
|
|
9921
|
+
items: {
|
|
9922
|
+
allOf: [
|
|
9923
|
+
{
|
|
9924
|
+
properties: {
|
|
9925
|
+
schema_version: {
|
|
9926
|
+
const: "aiwg.fortemi.index.record.v1"
|
|
9927
|
+
}
|
|
9928
|
+
}
|
|
9929
|
+
},
|
|
9930
|
+
{
|
|
9931
|
+
$ref: "#/$defs/v1RecordCompatibility"
|
|
9932
|
+
}
|
|
9933
|
+
]
|
|
9934
|
+
}
|
|
9935
|
+
}
|
|
9936
|
+
}
|
|
9937
|
+
}
|
|
9938
|
+
},
|
|
9939
|
+
{
|
|
9940
|
+
if: {
|
|
9941
|
+
properties: {
|
|
9942
|
+
schema_version: {
|
|
9943
|
+
const: "aiwg.fortemi.index.export.v2"
|
|
9944
|
+
}
|
|
9945
|
+
}
|
|
9946
|
+
},
|
|
9947
|
+
then: {
|
|
9948
|
+
required: ["compatibility"],
|
|
9949
|
+
properties: {
|
|
9950
|
+
source: {
|
|
9951
|
+
required: ["graph"]
|
|
9952
|
+
},
|
|
9953
|
+
items: {
|
|
9954
|
+
items: {
|
|
9955
|
+
properties: {
|
|
9956
|
+
schema_version: {
|
|
9957
|
+
const: "aiwg.fortemi.index.record.v2"
|
|
9958
|
+
}
|
|
9959
|
+
}
|
|
9960
|
+
}
|
|
9961
|
+
}
|
|
9962
|
+
}
|
|
9963
|
+
}
|
|
9964
|
+
}
|
|
9965
|
+
],
|
|
9966
|
+
$defs: {
|
|
9967
|
+
privacy: {
|
|
9968
|
+
enum: ["private", "sanitized", "public"]
|
|
9969
|
+
},
|
|
9970
|
+
recordType: {
|
|
9971
|
+
type: "string",
|
|
9972
|
+
minLength: 1
|
|
9973
|
+
},
|
|
9974
|
+
stringArray: {
|
|
9975
|
+
type: "array",
|
|
9976
|
+
items: {
|
|
9977
|
+
type: "string"
|
|
9978
|
+
}
|
|
9979
|
+
},
|
|
9980
|
+
numberArray: {
|
|
9981
|
+
type: "array",
|
|
9982
|
+
items: {
|
|
9983
|
+
type: "number"
|
|
9984
|
+
}
|
|
9985
|
+
},
|
|
9986
|
+
v1RecordCompatibility: {
|
|
9987
|
+
type: "object",
|
|
9988
|
+
not: {
|
|
9989
|
+
anyOf: [
|
|
9990
|
+
{
|
|
9991
|
+
required: ["name"]
|
|
9992
|
+
},
|
|
9993
|
+
{
|
|
9994
|
+
required: ["summary"]
|
|
9995
|
+
},
|
|
9996
|
+
{
|
|
9997
|
+
required: ["search"]
|
|
9998
|
+
},
|
|
9999
|
+
{
|
|
10000
|
+
required: ["chunks"]
|
|
10001
|
+
},
|
|
10002
|
+
{
|
|
10003
|
+
required: ["embeddings"]
|
|
10004
|
+
},
|
|
10005
|
+
{
|
|
10006
|
+
required: ["compatibility"]
|
|
10007
|
+
},
|
|
10008
|
+
{
|
|
10009
|
+
required: ["skos_concepts"]
|
|
10010
|
+
},
|
|
10011
|
+
{
|
|
10012
|
+
required: ["skos_relations"]
|
|
10013
|
+
},
|
|
10014
|
+
{
|
|
10015
|
+
required: ["provenance_events"]
|
|
10016
|
+
}
|
|
10017
|
+
]
|
|
10018
|
+
},
|
|
10019
|
+
properties: {
|
|
10020
|
+
source: {
|
|
10021
|
+
not: {
|
|
10022
|
+
anyOf: [
|
|
10023
|
+
{
|
|
10024
|
+
required: ["origin"]
|
|
10025
|
+
},
|
|
10026
|
+
{
|
|
10027
|
+
required: ["generated"]
|
|
10028
|
+
},
|
|
10029
|
+
{
|
|
10030
|
+
required: ["checksum"]
|
|
10031
|
+
},
|
|
10032
|
+
{
|
|
10033
|
+
required: ["updated_at"]
|
|
10034
|
+
}
|
|
10035
|
+
]
|
|
10036
|
+
}
|
|
10037
|
+
},
|
|
10038
|
+
relationships: {
|
|
10039
|
+
items: {
|
|
10040
|
+
not: {
|
|
10041
|
+
anyOf: [
|
|
10042
|
+
{
|
|
10043
|
+
required: ["target_path"]
|
|
10044
|
+
},
|
|
10045
|
+
{
|
|
10046
|
+
required: ["direction"]
|
|
10047
|
+
},
|
|
10048
|
+
{
|
|
10049
|
+
required: ["label"]
|
|
10050
|
+
},
|
|
10051
|
+
{
|
|
10052
|
+
required: ["confidence"]
|
|
10053
|
+
},
|
|
10054
|
+
{
|
|
10055
|
+
required: ["privacy"]
|
|
10056
|
+
},
|
|
10057
|
+
{
|
|
10058
|
+
required: ["metadata"]
|
|
10059
|
+
}
|
|
10060
|
+
]
|
|
10061
|
+
}
|
|
10062
|
+
}
|
|
10063
|
+
},
|
|
10064
|
+
privacy: {
|
|
10065
|
+
not: {
|
|
10066
|
+
required: ["locality"]
|
|
10067
|
+
}
|
|
10068
|
+
}
|
|
10069
|
+
}
|
|
10070
|
+
},
|
|
10071
|
+
record: {
|
|
10072
|
+
type: "object",
|
|
10073
|
+
additionalProperties: false,
|
|
10074
|
+
required: [
|
|
10075
|
+
"schema_version",
|
|
10076
|
+
"id",
|
|
10077
|
+
"type",
|
|
10078
|
+
"source",
|
|
10079
|
+
"title",
|
|
10080
|
+
"text",
|
|
10081
|
+
"facets",
|
|
10082
|
+
"tags",
|
|
10083
|
+
"concepts",
|
|
10084
|
+
"relationships",
|
|
10085
|
+
"provenance",
|
|
10086
|
+
"privacy",
|
|
10087
|
+
"updated_at"
|
|
10088
|
+
],
|
|
10089
|
+
properties: {
|
|
10090
|
+
schema_version: {
|
|
10091
|
+
enum: [
|
|
10092
|
+
"aiwg.fortemi.index.record.v1",
|
|
10093
|
+
"aiwg.fortemi.index.record.v2"
|
|
10094
|
+
]
|
|
10095
|
+
},
|
|
10096
|
+
id: {
|
|
10097
|
+
type: "string",
|
|
10098
|
+
minLength: 1
|
|
10099
|
+
},
|
|
10100
|
+
type: {
|
|
10101
|
+
$ref: "#/$defs/recordType"
|
|
10102
|
+
},
|
|
10103
|
+
source: {
|
|
10104
|
+
type: "object",
|
|
10105
|
+
additionalProperties: false,
|
|
10106
|
+
required: ["path", "repo_relative_path", "locator"],
|
|
10107
|
+
properties: {
|
|
10108
|
+
path: {
|
|
10109
|
+
type: "string",
|
|
10110
|
+
minLength: 1
|
|
10111
|
+
},
|
|
10112
|
+
repo_relative_path: {
|
|
10113
|
+
type: "string",
|
|
10114
|
+
minLength: 1
|
|
10115
|
+
},
|
|
10116
|
+
locator: {
|
|
10117
|
+
type: "string",
|
|
10118
|
+
minLength: 1
|
|
10119
|
+
},
|
|
10120
|
+
origin: {
|
|
10121
|
+
type: "string",
|
|
10122
|
+
minLength: 1
|
|
10123
|
+
},
|
|
10124
|
+
generated: {
|
|
10125
|
+
type: "boolean"
|
|
10126
|
+
},
|
|
10127
|
+
checksum: {
|
|
10128
|
+
type: "string"
|
|
10129
|
+
},
|
|
10130
|
+
updated_at: {
|
|
10131
|
+
type: "string",
|
|
10132
|
+
format: "date-time"
|
|
10133
|
+
}
|
|
10134
|
+
}
|
|
10135
|
+
},
|
|
10136
|
+
title: {
|
|
10137
|
+
type: "string"
|
|
10138
|
+
},
|
|
10139
|
+
name: {
|
|
10140
|
+
type: "string"
|
|
10141
|
+
},
|
|
10142
|
+
summary: {
|
|
10143
|
+
type: "string"
|
|
10144
|
+
},
|
|
10145
|
+
text: {
|
|
10146
|
+
type: "string"
|
|
10147
|
+
},
|
|
10148
|
+
search: {
|
|
10149
|
+
type: "object",
|
|
10150
|
+
additionalProperties: false,
|
|
10151
|
+
required: [
|
|
10152
|
+
"title",
|
|
10153
|
+
"body",
|
|
10154
|
+
"triggers",
|
|
10155
|
+
"aliases",
|
|
10156
|
+
"tags",
|
|
10157
|
+
"frontmatter"
|
|
10158
|
+
],
|
|
10159
|
+
properties: {
|
|
10160
|
+
title: {
|
|
10161
|
+
type: "string"
|
|
10162
|
+
},
|
|
10163
|
+
name: {
|
|
10164
|
+
type: "string"
|
|
10165
|
+
},
|
|
10166
|
+
summary: {
|
|
10167
|
+
type: "string"
|
|
10168
|
+
},
|
|
10169
|
+
body: {
|
|
10170
|
+
type: "string"
|
|
10171
|
+
},
|
|
10172
|
+
triggers: {
|
|
10173
|
+
$ref: "#/$defs/stringArray"
|
|
10174
|
+
},
|
|
10175
|
+
aliases: {
|
|
10176
|
+
$ref: "#/$defs/stringArray"
|
|
10177
|
+
},
|
|
10178
|
+
capability: {
|
|
10179
|
+
type: "string"
|
|
10180
|
+
},
|
|
10181
|
+
tags: {
|
|
10182
|
+
$ref: "#/$defs/stringArray"
|
|
10183
|
+
},
|
|
10184
|
+
phase: {
|
|
10185
|
+
type: "string"
|
|
10186
|
+
},
|
|
10187
|
+
type: {
|
|
10188
|
+
type: "string"
|
|
10189
|
+
},
|
|
10190
|
+
frontmatter: {
|
|
10191
|
+
type: "object"
|
|
10192
|
+
}
|
|
10193
|
+
}
|
|
10194
|
+
},
|
|
10195
|
+
facets: {
|
|
10196
|
+
type: "object",
|
|
10197
|
+
additionalProperties: {
|
|
10198
|
+
$ref: "#/$defs/stringArray"
|
|
10199
|
+
}
|
|
10200
|
+
},
|
|
10201
|
+
tags: {
|
|
10202
|
+
$ref: "#/$defs/stringArray"
|
|
10203
|
+
},
|
|
10204
|
+
concepts: {
|
|
10205
|
+
$ref: "#/$defs/stringArray"
|
|
10206
|
+
},
|
|
10207
|
+
relationships: {
|
|
10208
|
+
type: "array",
|
|
10209
|
+
items: {
|
|
10210
|
+
type: "object",
|
|
10211
|
+
additionalProperties: false,
|
|
10212
|
+
required: ["type", "target_id"],
|
|
10213
|
+
properties: {
|
|
10214
|
+
type: {
|
|
10215
|
+
type: "string",
|
|
10216
|
+
minLength: 1
|
|
10217
|
+
},
|
|
10218
|
+
target_id: {
|
|
10219
|
+
type: "string",
|
|
10220
|
+
minLength: 1
|
|
10221
|
+
},
|
|
10222
|
+
source_path: {
|
|
10223
|
+
type: "string"
|
|
10224
|
+
},
|
|
10225
|
+
target_path: {
|
|
10226
|
+
type: "string"
|
|
10227
|
+
},
|
|
10228
|
+
direction: {
|
|
10229
|
+
enum: ["upstream", "downstream", "related"]
|
|
10230
|
+
},
|
|
10231
|
+
label: {
|
|
10232
|
+
type: "string"
|
|
10233
|
+
},
|
|
10234
|
+
confidence: {
|
|
10235
|
+
type: "number"
|
|
10236
|
+
},
|
|
10237
|
+
privacy: {
|
|
10238
|
+
$ref: "#/$defs/privacy"
|
|
10239
|
+
},
|
|
10240
|
+
metadata: {
|
|
10241
|
+
type: "object"
|
|
10242
|
+
}
|
|
10243
|
+
}
|
|
10244
|
+
}
|
|
10245
|
+
},
|
|
10246
|
+
provenance: {
|
|
10247
|
+
type: "array",
|
|
10248
|
+
minItems: 1,
|
|
10249
|
+
items: {
|
|
10250
|
+
type: "object",
|
|
10251
|
+
additionalProperties: false,
|
|
10252
|
+
required: ["field", "source", "path", "confidence", "privacy"],
|
|
10253
|
+
properties: {
|
|
10254
|
+
field: {
|
|
10255
|
+
type: "string",
|
|
10256
|
+
minLength: 1
|
|
10257
|
+
},
|
|
10258
|
+
source: {
|
|
10259
|
+
type: "string",
|
|
10260
|
+
minLength: 1
|
|
10261
|
+
},
|
|
10262
|
+
path: {
|
|
10263
|
+
type: "string",
|
|
10264
|
+
minLength: 1
|
|
10265
|
+
},
|
|
10266
|
+
confidence: {
|
|
10267
|
+
enum: ["source", "candidate", "reviewed", "rejected"]
|
|
10268
|
+
},
|
|
10269
|
+
privacy: {
|
|
10270
|
+
$ref: "#/$defs/privacy"
|
|
10271
|
+
}
|
|
10272
|
+
}
|
|
10273
|
+
}
|
|
10274
|
+
},
|
|
10275
|
+
privacy: {
|
|
10276
|
+
type: "object",
|
|
10277
|
+
additionalProperties: false,
|
|
10278
|
+
required: ["classification", "pii"],
|
|
10279
|
+
properties: {
|
|
10280
|
+
classification: {
|
|
10281
|
+
$ref: "#/$defs/privacy"
|
|
10282
|
+
},
|
|
10283
|
+
pii: {
|
|
10284
|
+
type: "boolean"
|
|
10285
|
+
},
|
|
10286
|
+
locality: {
|
|
10287
|
+
enum: ["project", "framework", "external"]
|
|
10288
|
+
}
|
|
10289
|
+
}
|
|
10290
|
+
},
|
|
10291
|
+
chunks: {
|
|
10292
|
+
type: "array",
|
|
10293
|
+
items: {
|
|
10294
|
+
type: "object",
|
|
10295
|
+
additionalProperties: false,
|
|
10296
|
+
properties: {
|
|
10297
|
+
id: {
|
|
10298
|
+
type: "string",
|
|
10299
|
+
minLength: 1
|
|
10300
|
+
},
|
|
10301
|
+
text: {
|
|
10302
|
+
type: "string"
|
|
10303
|
+
},
|
|
10304
|
+
body: {
|
|
10305
|
+
type: "string"
|
|
10306
|
+
},
|
|
10307
|
+
summary: {
|
|
10308
|
+
type: "string"
|
|
10309
|
+
},
|
|
10310
|
+
source_path: {
|
|
10311
|
+
type: "string"
|
|
10312
|
+
},
|
|
10313
|
+
metadata: {
|
|
10314
|
+
type: "object"
|
|
10315
|
+
},
|
|
10316
|
+
checksum: {
|
|
10317
|
+
type: "string"
|
|
10318
|
+
}
|
|
10319
|
+
}
|
|
10320
|
+
}
|
|
10321
|
+
},
|
|
10322
|
+
embeddings: {
|
|
10323
|
+
type: "array",
|
|
10324
|
+
items: {
|
|
10325
|
+
type: "object",
|
|
10326
|
+
additionalProperties: false,
|
|
10327
|
+
properties: {
|
|
10328
|
+
id: {
|
|
10329
|
+
type: "string"
|
|
10330
|
+
},
|
|
10331
|
+
model: {
|
|
10332
|
+
type: "string"
|
|
10333
|
+
},
|
|
10334
|
+
embedding: {
|
|
10335
|
+
$ref: "#/$defs/numberArray"
|
|
10336
|
+
},
|
|
10337
|
+
vector: {
|
|
10338
|
+
$ref: "#/$defs/numberArray"
|
|
10339
|
+
},
|
|
10340
|
+
granularity: {
|
|
10341
|
+
type: "string"
|
|
10342
|
+
},
|
|
10343
|
+
source_path: {
|
|
10344
|
+
type: "string"
|
|
10345
|
+
},
|
|
10346
|
+
metadata: {
|
|
10347
|
+
type: "object"
|
|
10348
|
+
},
|
|
10349
|
+
chunk_id: {
|
|
10350
|
+
type: "string"
|
|
10351
|
+
},
|
|
10352
|
+
vector_ref: {
|
|
10353
|
+
type: "string"
|
|
10354
|
+
},
|
|
10355
|
+
input_hash: {
|
|
10356
|
+
type: "string"
|
|
10357
|
+
}
|
|
10358
|
+
}
|
|
10359
|
+
}
|
|
10360
|
+
},
|
|
10361
|
+
compatibility: {
|
|
10362
|
+
type: "object"
|
|
10363
|
+
},
|
|
10364
|
+
skos_concepts: {
|
|
10365
|
+
type: "array",
|
|
10366
|
+
items: {
|
|
10367
|
+
type: "object",
|
|
10368
|
+
additionalProperties: false,
|
|
10369
|
+
required: ["id", "prefLabel"],
|
|
10370
|
+
properties: {
|
|
10371
|
+
id: {
|
|
10372
|
+
type: "string",
|
|
10373
|
+
minLength: 1
|
|
10374
|
+
},
|
|
10375
|
+
prefLabel: {
|
|
10376
|
+
type: "string",
|
|
10377
|
+
minLength: 1
|
|
10378
|
+
},
|
|
10379
|
+
definition: {
|
|
10380
|
+
type: "string"
|
|
10381
|
+
},
|
|
10382
|
+
scheme: {
|
|
10383
|
+
type: "string"
|
|
10384
|
+
},
|
|
10385
|
+
notation: {
|
|
10386
|
+
type: "string"
|
|
10387
|
+
},
|
|
10388
|
+
uri: {
|
|
10389
|
+
type: "string"
|
|
10390
|
+
},
|
|
10391
|
+
altLabels: {
|
|
10392
|
+
$ref: "#/$defs/stringArray"
|
|
10393
|
+
},
|
|
10394
|
+
metadata: {
|
|
10395
|
+
type: "object"
|
|
10396
|
+
}
|
|
10397
|
+
}
|
|
10398
|
+
}
|
|
10399
|
+
},
|
|
10400
|
+
skos_relations: {
|
|
10401
|
+
type: "array",
|
|
10402
|
+
items: {
|
|
10403
|
+
type: "object",
|
|
10404
|
+
additionalProperties: false,
|
|
10405
|
+
required: ["type", "source_id", "target_id"],
|
|
10406
|
+
properties: {
|
|
10407
|
+
type: {
|
|
10408
|
+
type: "string",
|
|
10409
|
+
minLength: 1
|
|
10410
|
+
},
|
|
10411
|
+
source_id: {
|
|
10412
|
+
type: "string",
|
|
10413
|
+
minLength: 1
|
|
10414
|
+
},
|
|
10415
|
+
target_id: {
|
|
10416
|
+
type: "string",
|
|
10417
|
+
minLength: 1
|
|
10418
|
+
},
|
|
10419
|
+
source_path: {
|
|
10420
|
+
type: "string"
|
|
10421
|
+
},
|
|
10422
|
+
metadata: {
|
|
10423
|
+
type: "object"
|
|
10424
|
+
}
|
|
10425
|
+
}
|
|
10426
|
+
}
|
|
10427
|
+
},
|
|
10428
|
+
provenance_events: {
|
|
10429
|
+
type: "array",
|
|
10430
|
+
items: {
|
|
10431
|
+
type: "object",
|
|
10432
|
+
additionalProperties: false,
|
|
10433
|
+
required: ["activity"],
|
|
10434
|
+
properties: {
|
|
10435
|
+
id: {
|
|
10436
|
+
type: "string"
|
|
10437
|
+
},
|
|
10438
|
+
activity: {
|
|
10439
|
+
type: "string",
|
|
10440
|
+
minLength: 1
|
|
10441
|
+
},
|
|
10442
|
+
agent: {
|
|
10443
|
+
type: "string"
|
|
10444
|
+
},
|
|
10445
|
+
started_at: {
|
|
10446
|
+
type: "string",
|
|
10447
|
+
format: "date-time"
|
|
10448
|
+
},
|
|
10449
|
+
ended_at: {
|
|
10450
|
+
type: "string",
|
|
10451
|
+
format: "date-time"
|
|
10452
|
+
},
|
|
10453
|
+
source: {
|
|
10454
|
+
type: "string"
|
|
10455
|
+
},
|
|
10456
|
+
path: {
|
|
10457
|
+
type: "string"
|
|
10458
|
+
},
|
|
10459
|
+
confidence: {
|
|
10460
|
+
enum: ["source", "candidate", "reviewed", "rejected"]
|
|
10461
|
+
},
|
|
10462
|
+
privacy: {
|
|
10463
|
+
$ref: "#/$defs/privacy"
|
|
10464
|
+
},
|
|
10465
|
+
attributes: {
|
|
10466
|
+
type: "object"
|
|
10467
|
+
}
|
|
10468
|
+
}
|
|
10469
|
+
}
|
|
10470
|
+
},
|
|
10471
|
+
updated_at: {
|
|
10472
|
+
type: "string",
|
|
10473
|
+
format: "date-time"
|
|
10474
|
+
}
|
|
10475
|
+
}
|
|
10476
|
+
}
|
|
10477
|
+
}
|
|
10478
|
+
};
|
|
10479
|
+
|
|
10480
|
+
// src/aiwg-index-schema.ts
|
|
10481
|
+
var PROJECTED_FIELDS = [
|
|
10482
|
+
"schema_version",
|
|
10483
|
+
"id",
|
|
10484
|
+
"type",
|
|
10485
|
+
"title",
|
|
10486
|
+
"text",
|
|
10487
|
+
"facets",
|
|
10488
|
+
"tags",
|
|
10489
|
+
"concepts",
|
|
10490
|
+
"privacy"
|
|
10491
|
+
];
|
|
10492
|
+
var ajvInstance2;
|
|
10493
|
+
var exportValidator;
|
|
10494
|
+
var projectedRecordValidator;
|
|
10495
|
+
function getAiwgFortemiIndexExportSchema() {
|
|
10496
|
+
return aiwg_fortemi_index_export_schema_default;
|
|
10497
|
+
}
|
|
10498
|
+
function getAjv2() {
|
|
10499
|
+
if (!ajvInstance2) {
|
|
10500
|
+
ajvInstance2 = new Ajv2020({
|
|
10501
|
+
allErrors: true,
|
|
10502
|
+
strict: false,
|
|
10503
|
+
validateFormats: false
|
|
10504
|
+
});
|
|
10505
|
+
ajvInstance2.addSchema(aiwg_fortemi_index_export_schema_default);
|
|
10506
|
+
}
|
|
10507
|
+
return ajvInstance2;
|
|
10508
|
+
}
|
|
10509
|
+
function formatErrors2(errors) {
|
|
10510
|
+
return (errors ?? []).map((error) => {
|
|
10511
|
+
const path = error.instancePath || "(root)";
|
|
10512
|
+
return `${path} ${error.message ?? "is invalid"}`;
|
|
10513
|
+
});
|
|
10514
|
+
}
|
|
10515
|
+
function getExportValidator() {
|
|
10516
|
+
exportValidator ??= getAjv2().getSchema(aiwg_fortemi_index_export_schema_default.$id) ?? getAjv2().compile(aiwg_fortemi_index_export_schema_default);
|
|
10517
|
+
return exportValidator;
|
|
10518
|
+
}
|
|
10519
|
+
function getProjectedRecordValidator() {
|
|
10520
|
+
if (!projectedRecordValidator) {
|
|
10521
|
+
const properties = Object.fromEntries(Object.keys(aiwg_fortemi_index_export_schema_default.$defs.record.properties).map((field) => [
|
|
10522
|
+
field,
|
|
10523
|
+
{ $ref: `${aiwg_fortemi_index_export_schema_default.$id}#/$defs/record/properties/${field}` }
|
|
10524
|
+
]));
|
|
10525
|
+
projectedRecordValidator = getAjv2().compile({
|
|
10526
|
+
type: "object",
|
|
10527
|
+
required: PROJECTED_FIELDS,
|
|
10528
|
+
properties,
|
|
10529
|
+
additionalProperties: true,
|
|
10530
|
+
allOf: [
|
|
10531
|
+
{
|
|
10532
|
+
if: {
|
|
10533
|
+
properties: {
|
|
10534
|
+
schema_version: { const: "aiwg.fortemi.index.record.v1" }
|
|
10535
|
+
}
|
|
10536
|
+
},
|
|
10537
|
+
then: { $ref: `${aiwg_fortemi_index_export_schema_default.$id}#/$defs/v1RecordCompatibility` }
|
|
10538
|
+
}
|
|
10539
|
+
]
|
|
10540
|
+
});
|
|
10541
|
+
}
|
|
10542
|
+
return projectedRecordValidator;
|
|
10543
|
+
}
|
|
10544
|
+
function validateAiwgFortemiIndexExportSchema(value) {
|
|
10545
|
+
const validate = getExportValidator();
|
|
10546
|
+
const schemaValue = value && typeof value === "object" && Array.isArray(value.items) ? {
|
|
10547
|
+
...value,
|
|
10548
|
+
items: value.items.map((item) => {
|
|
10549
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return item;
|
|
10550
|
+
const schemaRecord = { ...item };
|
|
10551
|
+
Reflect.deleteProperty(schemaRecord, "binary_sources");
|
|
10552
|
+
return schemaRecord;
|
|
10553
|
+
})
|
|
10554
|
+
} : value;
|
|
10555
|
+
const valid = validate(schemaValue);
|
|
10556
|
+
return { valid, errors: formatErrors2(validate.errors) };
|
|
10557
|
+
}
|
|
10558
|
+
function validateAiwgFortemiProjectedRecordSchema(value) {
|
|
10559
|
+
const validate = getProjectedRecordValidator();
|
|
10560
|
+
const valid = validate(value);
|
|
10561
|
+
return { valid, errors: formatErrors2(validate.errors) };
|
|
10562
|
+
}
|
|
10563
|
+
|
|
8185
10564
|
// src/aiwg-index.ts
|
|
8186
10565
|
var AIWG_SCAN_REQUIRED_FIELDS = [
|
|
8187
10566
|
"schema_version",
|
|
@@ -8194,6 +10573,16 @@ var AIWG_SCAN_REQUIRED_FIELDS = [
|
|
|
8194
10573
|
"concepts",
|
|
8195
10574
|
"privacy"
|
|
8196
10575
|
];
|
|
10576
|
+
function isPrivacyExcluded(record, options) {
|
|
10577
|
+
const privacy = record.privacy;
|
|
10578
|
+
if (!privacy || !isPrivacyClassification(privacy.classification) || typeof privacy.pii !== "boolean") return true;
|
|
10579
|
+
if (privacy.classification === "private" && !options?.includePrivate) return true;
|
|
10580
|
+
if (privacy.pii && !options?.includePii) return true;
|
|
10581
|
+
return false;
|
|
10582
|
+
}
|
|
10583
|
+
function filterAiwgRecordsByPrivacy(records, options) {
|
|
10584
|
+
return records.filter((record) => !isPrivacyExcluded(record, options));
|
|
10585
|
+
}
|
|
8197
10586
|
var REQUIRED_RECORD_FIELDS = [
|
|
8198
10587
|
"schema_version",
|
|
8199
10588
|
"id",
|
|
@@ -8220,8 +10609,12 @@ function hasString(value) {
|
|
|
8220
10609
|
return typeof value === "string" && value.length > 0;
|
|
8221
10610
|
}
|
|
8222
10611
|
function pushFacet(counts, name, value) {
|
|
8223
|
-
counts[name]
|
|
8224
|
-
|
|
10612
|
+
let bucket = counts[name];
|
|
10613
|
+
if (bucket === void 0) {
|
|
10614
|
+
bucket = /* @__PURE__ */ Object.create(null);
|
|
10615
|
+
counts[name] = bucket;
|
|
10616
|
+
}
|
|
10617
|
+
bucket[value] = (bucket[value] ?? 0) + 1;
|
|
8225
10618
|
}
|
|
8226
10619
|
function hasNonNegativeInteger(value) {
|
|
8227
10620
|
return Number.isInteger(value) && typeof value === "number" && value >= 0;
|
|
@@ -8251,6 +10644,10 @@ function validateOptionalRichMetadata(item, index, errors) {
|
|
|
8251
10644
|
errors.push("items[" + index + "].skos_concepts must be an array when present");
|
|
8252
10645
|
} else {
|
|
8253
10646
|
for (const [conceptIndex, concept] of item.skos_concepts.entries()) {
|
|
10647
|
+
if (!isPlainRecord(concept)) {
|
|
10648
|
+
errors.push("items[" + index + "].skos_concepts[" + conceptIndex + "] must be an object");
|
|
10649
|
+
continue;
|
|
10650
|
+
}
|
|
8254
10651
|
if (!hasString(concept.id)) errors.push("items[" + index + "].skos_concepts[" + conceptIndex + "].id is required");
|
|
8255
10652
|
if (!hasString(concept.prefLabel)) errors.push("items[" + index + "].skos_concepts[" + conceptIndex + "].prefLabel is required");
|
|
8256
10653
|
if (!isOptionalStringArray(concept.altLabels)) errors.push("items[" + index + "].skos_concepts[" + conceptIndex + "].altLabels must be a string array");
|
|
@@ -8265,6 +10662,10 @@ function validateOptionalRichMetadata(item, index, errors) {
|
|
|
8265
10662
|
errors.push("items[" + index + "].skos_relations must be an array when present");
|
|
8266
10663
|
} else {
|
|
8267
10664
|
for (const [relationIndex, relation] of item.skos_relations.entries()) {
|
|
10665
|
+
if (!isPlainRecord(relation)) {
|
|
10666
|
+
errors.push("items[" + index + "].skos_relations[" + relationIndex + "] must be an object");
|
|
10667
|
+
continue;
|
|
10668
|
+
}
|
|
8268
10669
|
if (!hasString(relation.type)) errors.push("items[" + index + "].skos_relations[" + relationIndex + "].type is required");
|
|
8269
10670
|
if (!hasString(relation.source_id)) errors.push("items[" + index + "].skos_relations[" + relationIndex + "].source_id is required");
|
|
8270
10671
|
if (!hasString(relation.target_id)) errors.push("items[" + index + "].skos_relations[" + relationIndex + "].target_id is required");
|
|
@@ -8279,6 +10680,10 @@ function validateOptionalRichMetadata(item, index, errors) {
|
|
|
8279
10680
|
errors.push("items[" + index + "].provenance_events must be an array when present");
|
|
8280
10681
|
} else {
|
|
8281
10682
|
for (const [eventIndex, event] of item.provenance_events.entries()) {
|
|
10683
|
+
if (!isPlainRecord(event)) {
|
|
10684
|
+
errors.push("items[" + index + "].provenance_events[" + eventIndex + "] must be an object");
|
|
10685
|
+
continue;
|
|
10686
|
+
}
|
|
8282
10687
|
if (!hasString(event.activity)) errors.push("items[" + index + "].provenance_events[" + eventIndex + "].activity is required");
|
|
8283
10688
|
if (event.attributes !== void 0 && !isPlainRecord(event.attributes)) {
|
|
8284
10689
|
errors.push("items[" + index + "].provenance_events[" + eventIndex + "].attributes must be an object");
|
|
@@ -8288,6 +10693,10 @@ function validateOptionalRichMetadata(item, index, errors) {
|
|
|
8288
10693
|
}
|
|
8289
10694
|
if (Array.isArray(item.relationships)) {
|
|
8290
10695
|
for (const [relationshipIndex, relationship] of item.relationships.entries()) {
|
|
10696
|
+
if (!isPlainRecord(relationship)) {
|
|
10697
|
+
errors.push("items[" + index + "].relationships[" + relationshipIndex + "] must be an object");
|
|
10698
|
+
continue;
|
|
10699
|
+
}
|
|
8291
10700
|
if (relationship.metadata !== void 0 && !isPlainRecord(relationship.metadata)) {
|
|
8292
10701
|
errors.push("items[" + index + "].relationships[" + relationshipIndex + "].metadata must be an object");
|
|
8293
10702
|
}
|
|
@@ -8316,7 +10725,10 @@ function validateOptionalRichMetadata(item, index, errors) {
|
|
|
8316
10725
|
errors.push("items[" + index + "].chunks must be an array when present");
|
|
8317
10726
|
} else {
|
|
8318
10727
|
for (const [chunkIndex, chunk] of item.chunks.entries()) {
|
|
8319
|
-
if (!isPlainRecord(chunk))
|
|
10728
|
+
if (!isPlainRecord(chunk)) {
|
|
10729
|
+
errors.push("items[" + index + "].chunks[" + chunkIndex + "] must be an object");
|
|
10730
|
+
continue;
|
|
10731
|
+
}
|
|
8320
10732
|
if (chunk.metadata !== void 0 && !isPlainRecord(chunk.metadata)) {
|
|
8321
10733
|
errors.push("items[" + index + "].chunks[" + chunkIndex + "].metadata must be an object");
|
|
8322
10734
|
}
|
|
@@ -8328,9 +10740,12 @@ function validateOptionalRichMetadata(item, index, errors) {
|
|
|
8328
10740
|
errors.push("items[" + index + "].embeddings must be an array when present");
|
|
8329
10741
|
} else {
|
|
8330
10742
|
for (const [embeddingIndex, embedding] of item.embeddings.entries()) {
|
|
8331
|
-
if (!isPlainRecord(embedding))
|
|
8332
|
-
|
|
8333
|
-
|
|
10743
|
+
if (!isPlainRecord(embedding)) {
|
|
10744
|
+
errors.push("items[" + index + "].embeddings[" + embeddingIndex + "] must be an object");
|
|
10745
|
+
continue;
|
|
10746
|
+
}
|
|
10747
|
+
const vector = embedding.embedding ?? embedding.vector;
|
|
10748
|
+
if (vector !== void 0 && (!Array.isArray(vector) || !vector.every((entry) => typeof entry === "number"))) {
|
|
8334
10749
|
errors.push("items[" + index + "].embeddings[" + embeddingIndex + "].embedding/vector must be a number array");
|
|
8335
10750
|
}
|
|
8336
10751
|
if (embedding.metadata !== void 0 && !isPlainRecord(embedding.metadata)) {
|
|
@@ -8343,23 +10758,91 @@ function validateOptionalRichMetadata(item, index, errors) {
|
|
|
8343
10758
|
errors.push("items[" + index + "].compatibility must be an object");
|
|
8344
10759
|
}
|
|
8345
10760
|
}
|
|
10761
|
+
function isPrivacyClassification(value) {
|
|
10762
|
+
return value === "private" || value === "sanitized" || value === "public";
|
|
10763
|
+
}
|
|
10764
|
+
function isProvenanceConfidence(value) {
|
|
10765
|
+
return value === "source" || value === "candidate" || value === "reviewed" || value === "rejected";
|
|
10766
|
+
}
|
|
10767
|
+
function validateProvenanceItems(item, index, errors) {
|
|
10768
|
+
if (!Array.isArray(item.provenance)) return;
|
|
10769
|
+
for (const [provIndex, prov] of item.provenance.entries()) {
|
|
10770
|
+
const at = "items[" + index + "].provenance[" + provIndex + "]";
|
|
10771
|
+
if (!isPlainRecord(prov)) {
|
|
10772
|
+
errors.push(at + " must be an object");
|
|
10773
|
+
continue;
|
|
10774
|
+
}
|
|
10775
|
+
if (!hasString(prov.field)) errors.push(at + ".field is required");
|
|
10776
|
+
if (!hasString(prov.source)) errors.push(at + ".source is required");
|
|
10777
|
+
if (!hasString(prov.path)) errors.push(at + ".path is required");
|
|
10778
|
+
if (!isProvenanceConfidence(prov.confidence)) errors.push(at + ".confidence must be one of source, candidate, reviewed, rejected");
|
|
10779
|
+
if (!isPrivacyClassification(prov.privacy)) errors.push(at + ".privacy must be one of private, sanitized, public");
|
|
10780
|
+
}
|
|
10781
|
+
}
|
|
10782
|
+
var V2_ONLY_RECORD_FIELDS = ["search", "chunks", "embeddings", "skos_concepts", "skos_relations", "compatibility"];
|
|
10783
|
+
var V2_ONLY_SOURCE_FIELDS = ["origin", "generated", "checksum", "updated_at"];
|
|
10784
|
+
var V2_ONLY_RELATIONSHIP_FIELDS = ["target_path", "direction", "metadata"];
|
|
10785
|
+
function forbidV2FieldsOnV1Record(item, index, errors) {
|
|
10786
|
+
if (item.schema_version !== "aiwg.fortemi.index.record.v1") return;
|
|
10787
|
+
const at = "items[" + index + "]";
|
|
10788
|
+
const bag = item;
|
|
10789
|
+
const v2msg = " is a v2-only field and must be absent on a record.v1 record";
|
|
10790
|
+
for (const field of V2_ONLY_RECORD_FIELDS) {
|
|
10791
|
+
if (bag[field] !== void 0) errors.push(at + "." + field + v2msg);
|
|
10792
|
+
}
|
|
10793
|
+
if (isPlainRecord(item.source)) {
|
|
10794
|
+
const src = item.source;
|
|
10795
|
+
for (const field of V2_ONLY_SOURCE_FIELDS) {
|
|
10796
|
+
if (src[field] !== void 0) errors.push(at + ".source." + field + v2msg);
|
|
10797
|
+
}
|
|
10798
|
+
}
|
|
10799
|
+
if (isPlainRecord(item.privacy) && item.privacy.locality !== void 0) {
|
|
10800
|
+
errors.push(at + ".privacy.locality" + v2msg);
|
|
10801
|
+
}
|
|
10802
|
+
if (Array.isArray(item.relationships)) {
|
|
10803
|
+
for (const [relIndex, rel] of item.relationships.entries()) {
|
|
10804
|
+
if (!isPlainRecord(rel)) continue;
|
|
10805
|
+
const relBag = rel;
|
|
10806
|
+
for (const field of V2_ONLY_RELATIONSHIP_FIELDS) {
|
|
10807
|
+
if (relBag[field] !== void 0) errors.push(at + ".relationships[" + relIndex + "]." + field + v2msg);
|
|
10808
|
+
}
|
|
10809
|
+
}
|
|
10810
|
+
}
|
|
10811
|
+
}
|
|
8346
10812
|
function validateAiwgFortemiIndexExport(value) {
|
|
8347
|
-
const errors =
|
|
8348
|
-
const counts =
|
|
8349
|
-
const data = value;
|
|
10813
|
+
const errors = validateAiwgFortemiIndexExportSchema(value).errors;
|
|
10814
|
+
const counts = /* @__PURE__ */ Object.create(null);
|
|
10815
|
+
const data = isPlainRecord(value) ? value : {};
|
|
10816
|
+
if (!isPlainRecord(value)) errors.push("index export must be an object");
|
|
8350
10817
|
if (!isSupportedIndexSchemaVersion(data?.schema_version)) {
|
|
8351
10818
|
errors.push("schema_version must be aiwg.fortemi.index.export.v1 or aiwg.fortemi.index.export.v2");
|
|
8352
10819
|
}
|
|
8353
10820
|
if (!hasString(data?.generated_at)) errors.push("generated_at is required");
|
|
8354
10821
|
if (!hasString(data?.source?.repo)) errors.push("source.repo is required");
|
|
8355
10822
|
if (!hasString(data?.source?.privacy)) errors.push("source.privacy is required");
|
|
10823
|
+
else if (!isPrivacyClassification(data?.source?.privacy)) {
|
|
10824
|
+
errors.push("source.privacy must be one of private, sanitized, public");
|
|
10825
|
+
}
|
|
8356
10826
|
if (!Array.isArray(data?.items)) errors.push("items must be an array");
|
|
8357
10827
|
if (data.compatibility !== void 0 && !isPlainRecord(data.compatibility)) {
|
|
8358
10828
|
errors.push("compatibility must be an object");
|
|
8359
10829
|
}
|
|
10830
|
+
if (data?.schema_version === "aiwg.fortemi.index.export.v1") {
|
|
10831
|
+
if (isPlainRecord(data.source) && data.source.graph !== void 0) {
|
|
10832
|
+
errors.push("source.graph is a v2-only field and must be absent on an export.v1 export");
|
|
10833
|
+
}
|
|
10834
|
+
if (data.compatibility !== void 0) {
|
|
10835
|
+
errors.push("compatibility is a v2-only field and must be absent on an export.v1 export");
|
|
10836
|
+
}
|
|
10837
|
+
}
|
|
8360
10838
|
const ids = /* @__PURE__ */ new Set();
|
|
8361
10839
|
let previousId = "";
|
|
8362
|
-
|
|
10840
|
+
const items = Array.isArray(data.items) ? data.items : [];
|
|
10841
|
+
for (const [index, item] of items.entries()) {
|
|
10842
|
+
if (!isPlainRecord(item)) {
|
|
10843
|
+
errors.push("items[" + index + "] must be an object");
|
|
10844
|
+
continue;
|
|
10845
|
+
}
|
|
8363
10846
|
for (const field of REQUIRED_RECORD_FIELDS) {
|
|
8364
10847
|
if (!(field in item)) errors.push("items[" + index + "]." + field + " is required");
|
|
8365
10848
|
}
|
|
@@ -8369,7 +10852,7 @@ function validateAiwgFortemiIndexExport(value) {
|
|
|
8369
10852
|
if (!hasString(item.id)) errors.push("items[" + index + "].id is required");
|
|
8370
10853
|
if (hasString(item.id) && ids.has(item.id)) errors.push("duplicate id: " + item.id);
|
|
8371
10854
|
if (hasString(item.id)) ids.add(item.id);
|
|
8372
|
-
if (previousId && hasString(item.id) && previousId
|
|
10855
|
+
if (previousId && hasString(item.id) && previousId > item.id) {
|
|
8373
10856
|
errors.push("items must be sorted by id: " + previousId + " before " + item.id);
|
|
8374
10857
|
}
|
|
8375
10858
|
if (hasString(item.id)) previousId = item.id;
|
|
@@ -8391,8 +10874,12 @@ function validateAiwgFortemiIndexExport(value) {
|
|
|
8391
10874
|
errors.push("items[" + index + "].provenance must be a non-empty array");
|
|
8392
10875
|
}
|
|
8393
10876
|
validateOptionalRichMetadata(item, index, errors);
|
|
10877
|
+
validateProvenanceItems(item, index, errors);
|
|
10878
|
+
forbidV2FieldsOnV1Record(item, index, errors);
|
|
8394
10879
|
if (!item.privacy || typeof item.privacy.pii !== "boolean" || !hasString(item.privacy.classification)) {
|
|
8395
10880
|
errors.push("items[" + index + "].privacy requires classification and pii");
|
|
10881
|
+
} else if (!isPrivacyClassification(item.privacy.classification)) {
|
|
10882
|
+
errors.push("items[" + index + "].privacy.classification must be one of private, sanitized, public");
|
|
8396
10883
|
}
|
|
8397
10884
|
}
|
|
8398
10885
|
return { valid: errors.length === 0, errors, counts };
|
|
@@ -8406,13 +10893,17 @@ function assertAiwgFortemiIndexExport(value) {
|
|
|
8406
10893
|
}
|
|
8407
10894
|
function validateAiwgFortemiChunkManifest(value) {
|
|
8408
10895
|
const errors = [];
|
|
8409
|
-
const data = value;
|
|
10896
|
+
const data = isPlainRecord(value) ? value : {};
|
|
10897
|
+
if (!isPlainRecord(value)) errors.push("chunk manifest must be an object");
|
|
8410
10898
|
if (data?.schema_version !== "aiwg.fortemi.index.chunk-manifest.v1") {
|
|
8411
10899
|
errors.push("schema_version must be aiwg.fortemi.index.chunk-manifest.v1");
|
|
8412
10900
|
}
|
|
8413
10901
|
if (!hasString(data?.generated_at)) errors.push("generated_at is required");
|
|
8414
10902
|
if (!hasString(data?.source?.repo)) errors.push("source.repo is required");
|
|
8415
10903
|
if (!hasString(data?.source?.privacy)) errors.push("source.privacy is required");
|
|
10904
|
+
if (data?.source_export_schema_version !== void 0 && !isSupportedIndexSchemaVersion(data.source_export_schema_version)) {
|
|
10905
|
+
errors.push("source_export_schema_version must be aiwg.fortemi.index.export.v1 or aiwg.fortemi.index.export.v2 when present");
|
|
10906
|
+
}
|
|
8416
10907
|
if (!hasNonNegativeInteger(data?.total)) errors.push("total must be a non-negative integer");
|
|
8417
10908
|
if (!hasPositiveInteger(data?.part_size)) errors.push("part_size must be a positive integer");
|
|
8418
10909
|
if (data.facets !== void 0 && !isFacetCounts(data.facets)) {
|
|
@@ -8428,7 +10919,9 @@ function validateAiwgFortemiChunkManifest(value) {
|
|
|
8428
10919
|
}
|
|
8429
10920
|
}
|
|
8430
10921
|
}
|
|
8431
|
-
if (data.detail !== void 0) {
|
|
10922
|
+
if (data.detail !== void 0 && !isPlainRecord(data.detail)) {
|
|
10923
|
+
errors.push("detail must be an object");
|
|
10924
|
+
} else if (data.detail !== void 0) {
|
|
8432
10925
|
if (!hasString(data.detail.href)) errors.push("detail.href is required");
|
|
8433
10926
|
else if (!data.detail.href.includes("{id}")) errors.push("detail.href must contain the {id} placeholder");
|
|
8434
10927
|
if (data.detail.encoding !== void 0 && data.detail.encoding !== "uri" && data.detail.encoding !== "base64url") {
|
|
@@ -8439,6 +10932,10 @@ function validateAiwgFortemiChunkManifest(value) {
|
|
|
8439
10932
|
let expectedOffset = 0;
|
|
8440
10933
|
const parts = Array.isArray(data?.parts) ? data.parts : [];
|
|
8441
10934
|
for (const [index, part] of parts.entries()) {
|
|
10935
|
+
if (!isPlainRecord(part)) {
|
|
10936
|
+
errors.push("parts[" + index + "] must be an object");
|
|
10937
|
+
continue;
|
|
10938
|
+
}
|
|
8442
10939
|
if (!hasString(part.href)) errors.push("parts[" + index + "].href is required");
|
|
8443
10940
|
if (!hasNonNegativeInteger(part.offset)) errors.push("parts[" + index + "].offset must be a non-negative integer");
|
|
8444
10941
|
if (!hasNonNegativeInteger(part.count)) errors.push("parts[" + index + "].count must be a non-negative integer");
|
|
@@ -8459,18 +10956,28 @@ function assertAiwgFortemiChunkManifest(value) {
|
|
|
8459
10956
|
}
|
|
8460
10957
|
return value;
|
|
8461
10958
|
}
|
|
8462
|
-
function validateProjectedRecords(items) {
|
|
10959
|
+
function validateProjectedRecords(items, sourceSchemaVersion) {
|
|
8463
10960
|
const errors = [];
|
|
8464
10961
|
const ids = /* @__PURE__ */ new Set();
|
|
8465
10962
|
let previousId = "";
|
|
8466
10963
|
for (const [index, item] of items.entries()) {
|
|
10964
|
+
if (!isPlainRecord(item)) {
|
|
10965
|
+
errors.push("items[" + index + "] must be an object");
|
|
10966
|
+
continue;
|
|
10967
|
+
}
|
|
10968
|
+
const schemaValidation = validateAiwgFortemiProjectedRecordSchema(item);
|
|
10969
|
+
errors.push(...schemaValidation.errors.map((error) => `items[${index}]${error}`));
|
|
10970
|
+
const expectedRecordVersion = sourceSchemaVersion === "aiwg.fortemi.index.export.v2" ? "aiwg.fortemi.index.record.v2" : "aiwg.fortemi.index.record.v1";
|
|
10971
|
+
if (item.schema_version !== expectedRecordVersion) {
|
|
10972
|
+
errors.push(`items[${index}].schema_version must match ${sourceSchemaVersion}`);
|
|
10973
|
+
}
|
|
8467
10974
|
if (!isSupportedRecordSchemaVersion(item.schema_version)) {
|
|
8468
10975
|
errors.push("items[" + index + "].schema_version must be aiwg.fortemi.index.record.v1 or aiwg.fortemi.index.record.v2");
|
|
8469
10976
|
}
|
|
8470
10977
|
if (!hasString(item.id)) errors.push("items[" + index + "].id is required");
|
|
8471
10978
|
if (hasString(item.id) && ids.has(item.id)) errors.push("duplicate id: " + item.id);
|
|
8472
10979
|
if (hasString(item.id)) ids.add(item.id);
|
|
8473
|
-
if (previousId && hasString(item.id) && previousId
|
|
10980
|
+
if (previousId && hasString(item.id) && previousId > item.id) {
|
|
8474
10981
|
errors.push("items must be sorted by id: " + previousId + " before " + item.id);
|
|
8475
10982
|
}
|
|
8476
10983
|
if (hasString(item.id)) previousId = item.id;
|
|
@@ -8486,15 +10993,13 @@ function validateProjectedRecords(items) {
|
|
|
8486
10993
|
}
|
|
8487
10994
|
if (!Array.isArray(item.tags)) errors.push("items[" + index + "].tags must be an array");
|
|
8488
10995
|
if (!Array.isArray(item.concepts)) errors.push("items[" + index + "].concepts must be an array");
|
|
8489
|
-
if (!item.privacy || !hasString(item.privacy.classification)) {
|
|
8490
|
-
errors.push("items[" + index + "].privacy.classification is required");
|
|
8491
|
-
}
|
|
8492
10996
|
}
|
|
8493
10997
|
return errors;
|
|
8494
10998
|
}
|
|
8495
10999
|
function validateAiwgFortemiChunkPart(value, partRef, manifest) {
|
|
8496
11000
|
const errors = [];
|
|
8497
|
-
const data = value;
|
|
11001
|
+
const data = isPlainRecord(value) ? value : {};
|
|
11002
|
+
if (!isPlainRecord(value)) errors.push("chunk part must be an object");
|
|
8498
11003
|
if (data?.schema_version !== "aiwg.fortemi.index.chunk.v1") {
|
|
8499
11004
|
errors.push("schema_version must be aiwg.fortemi.index.chunk.v1");
|
|
8500
11005
|
}
|
|
@@ -8511,12 +11016,16 @@ function validateAiwgFortemiChunkPart(value, partRef, manifest) {
|
|
|
8511
11016
|
}
|
|
8512
11017
|
if (Array.isArray(data?.items)) {
|
|
8513
11018
|
if (manifest?.projection) {
|
|
8514
|
-
errors.push(...validateProjectedRecords(
|
|
11019
|
+
errors.push(...validateProjectedRecords(
|
|
11020
|
+
data.items,
|
|
11021
|
+
manifest.source_export_schema_version ?? "aiwg.fortemi.index.export.v1"
|
|
11022
|
+
).map((error) => "items." + error));
|
|
8515
11023
|
} else {
|
|
8516
11024
|
const validation = validateAiwgFortemiIndexExport({
|
|
8517
|
-
schema_version: "aiwg.fortemi.index.export.v1",
|
|
11025
|
+
schema_version: manifest?.source_export_schema_version ?? "aiwg.fortemi.index.export.v1",
|
|
8518
11026
|
generated_at: manifest?.generated_at ?? "1970-01-01T00:00:00.000Z",
|
|
8519
11027
|
source: manifest?.source ?? { repo: "chunk", privacy: "public" },
|
|
11028
|
+
...(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" } } : {},
|
|
8520
11029
|
items: data.items
|
|
8521
11030
|
});
|
|
8522
11031
|
errors.push(...validation.errors.map((error) => "items." + error));
|
|
@@ -8531,9 +11040,35 @@ function assertAiwgFortemiChunkPart(value, partRef, manifest) {
|
|
|
8531
11040
|
}
|
|
8532
11041
|
return value;
|
|
8533
11042
|
}
|
|
11043
|
+
var ALLOWED_AIWG_FETCH_SCHEMES = /* @__PURE__ */ new Set(["http:", "https:", "blob:", "data:"]);
|
|
11044
|
+
function tryParseUrl(value) {
|
|
11045
|
+
try {
|
|
11046
|
+
return new URL(value);
|
|
11047
|
+
} catch {
|
|
11048
|
+
return null;
|
|
11049
|
+
}
|
|
11050
|
+
}
|
|
11051
|
+
function resolveAiwgFetchUrl(href, baseUrl) {
|
|
11052
|
+
if (baseUrl === void 0) {
|
|
11053
|
+
const absolute = tryParseUrl(href);
|
|
11054
|
+
if (absolute && !ALLOWED_AIWG_FETCH_SCHEMES.has(absolute.protocol)) {
|
|
11055
|
+
throw new Error("Refusing AIWG index fetch with disallowed scheme: " + absolute.protocol);
|
|
11056
|
+
}
|
|
11057
|
+
return href;
|
|
11058
|
+
}
|
|
11059
|
+
const base = new URL(baseUrl);
|
|
11060
|
+
const resolved = new URL(href, base);
|
|
11061
|
+
if (!ALLOWED_AIWG_FETCH_SCHEMES.has(resolved.protocol)) {
|
|
11062
|
+
throw new Error("Refusing AIWG index fetch with disallowed scheme: " + resolved.protocol);
|
|
11063
|
+
}
|
|
11064
|
+
if (resolved.origin !== base.origin) {
|
|
11065
|
+
throw new Error("Refusing cross-origin AIWG index fetch: " + resolved.origin + " != " + base.origin);
|
|
11066
|
+
}
|
|
11067
|
+
return resolved.toString();
|
|
11068
|
+
}
|
|
8534
11069
|
function createAiwgFetchChunkLoader(baseUrl) {
|
|
8535
11070
|
return async (part) => {
|
|
8536
|
-
const href =
|
|
11071
|
+
const href = resolveAiwgFetchUrl(part.href, baseUrl);
|
|
8537
11072
|
const response = await fetch(href);
|
|
8538
11073
|
if (!response.ok) throw new Error("Failed to fetch AIWG index chunk " + href + ": " + response.status);
|
|
8539
11074
|
return response.json();
|
|
@@ -8553,14 +11088,14 @@ function createAiwgFetchDetailLoader(baseUrl) {
|
|
|
8553
11088
|
return async (id, manifest) => {
|
|
8554
11089
|
if (!manifest.detail?.href) throw new Error("Manifest has no detail.href for record resolution");
|
|
8555
11090
|
const relative = aiwgDetailHrefForId(manifest.detail, id);
|
|
8556
|
-
const href =
|
|
11091
|
+
const href = resolveAiwgFetchUrl(relative, baseUrl);
|
|
8557
11092
|
const response = await fetch(href);
|
|
8558
11093
|
if (!response.ok) throw new Error("Failed to fetch AIWG index detail " + href + ": " + response.status);
|
|
8559
11094
|
return response.json();
|
|
8560
11095
|
};
|
|
8561
11096
|
}
|
|
8562
11097
|
function getAiwgFortemiFacets(items) {
|
|
8563
|
-
const result =
|
|
11098
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
8564
11099
|
for (const item of items) {
|
|
8565
11100
|
pushFacet(result, "type", item.type);
|
|
8566
11101
|
pushFacet(result, "privacy", item.privacy.classification);
|
|
@@ -8577,15 +11112,17 @@ function recordTitle(item) {
|
|
|
8577
11112
|
}
|
|
8578
11113
|
function recordText(item) {
|
|
8579
11114
|
const base = item.text ?? item.search?.body ?? item.search?.summary ?? item.chunks?.map((chunk) => chunk.text ?? chunk.body ?? chunk.summary ?? "").filter(Boolean).join("\n") ?? "";
|
|
8580
|
-
const extractedText =
|
|
11115
|
+
const extractedText = binarySourceText(item);
|
|
8581
11116
|
return [base, extractedText].filter(Boolean).join("\n");
|
|
8582
11117
|
}
|
|
11118
|
+
function binarySourceText(item) {
|
|
11119
|
+
return "binary_sources" in item ? item.binary_sources?.map((source) => source.extracted_text).filter(Boolean).join("\n") ?? "" : "";
|
|
11120
|
+
}
|
|
8583
11121
|
function defaultEmbeddingInput(record, granularity) {
|
|
8584
11122
|
const title = recordTitle(record);
|
|
8585
11123
|
const text = recordText(record);
|
|
8586
11124
|
if (granularity === "title-summary") {
|
|
8587
|
-
|
|
8588
|
-
return [title, record.search?.summary ?? "", extractedText].filter(Boolean).join("\n");
|
|
11125
|
+
return [title, record.search?.summary ?? "", binarySourceText(record)].filter(Boolean).join("\n");
|
|
8589
11126
|
}
|
|
8590
11127
|
return [title, text].filter(Boolean).join("\n");
|
|
8591
11128
|
}
|
|
@@ -8596,7 +11133,7 @@ function generatedAtString(value) {
|
|
|
8596
11133
|
async function buildAiwgStaticEmbeddingSet(index, options) {
|
|
8597
11134
|
assertAiwgFortemiIndexExport(index);
|
|
8598
11135
|
const granularity = options.granularity ?? "body";
|
|
8599
|
-
const records = options.records ?? index.items;
|
|
11136
|
+
const records = filterAiwgRecordsByPrivacy(options.records ?? index.items, options.privacy);
|
|
8600
11137
|
const embeddings = [];
|
|
8601
11138
|
for (const record of records) {
|
|
8602
11139
|
const input = options.textForRecord?.(record) ?? defaultEmbeddingInput(record, granularity);
|
|
@@ -8653,11 +11190,12 @@ function recordSearchValues(item) {
|
|
|
8653
11190
|
return values.filter((value) => typeof value === "string" && value.length > 0);
|
|
8654
11191
|
}
|
|
8655
11192
|
function buildAiwgChunkedIndex(index, options = {}) {
|
|
11193
|
+
assertAiwgFortemiIndexExport(index);
|
|
8656
11194
|
const partSize = hasPositiveInteger(options.partSize) ? options.partSize : 500;
|
|
8657
11195
|
const projection = options.projection;
|
|
8658
11196
|
const idEncoding = options.idEncoding ?? "base64url";
|
|
8659
11197
|
const detailHref = options.detailHref ?? "detail/{id}.json";
|
|
8660
|
-
const items = index.items;
|
|
11198
|
+
const items = filterAiwgRecordsByPrivacy(index.items, options.privacy);
|
|
8661
11199
|
const pad = (value) => String(value).padStart(4, "0");
|
|
8662
11200
|
const project = (record) => {
|
|
8663
11201
|
if (!projection) return record;
|
|
@@ -8685,6 +11223,7 @@ function buildAiwgChunkedIndex(index, options = {}) {
|
|
|
8685
11223
|
schema_version: "aiwg.fortemi.index.chunk-manifest.v1",
|
|
8686
11224
|
generated_at: options.generatedAt ?? index.generated_at,
|
|
8687
11225
|
source: index.source,
|
|
11226
|
+
source_export_schema_version: index.schema_version,
|
|
8688
11227
|
total: items.length,
|
|
8689
11228
|
part_size: partSize,
|
|
8690
11229
|
facets: getAiwgFortemiFacets(items),
|
|
@@ -8731,35 +11270,88 @@ function queryMatches(item, q) {
|
|
|
8731
11270
|
return matches;
|
|
8732
11271
|
}
|
|
8733
11272
|
var DISCOVERY_STOPWORDS = /* @__PURE__ */ new Set([
|
|
11273
|
+
"the",
|
|
8734
11274
|
"a",
|
|
8735
11275
|
"an",
|
|
8736
11276
|
"and",
|
|
8737
|
-
"
|
|
8738
|
-
"
|
|
11277
|
+
"or",
|
|
11278
|
+
"of",
|
|
8739
11279
|
"for",
|
|
8740
|
-
"
|
|
8741
|
-
"how",
|
|
8742
|
-
"i",
|
|
11280
|
+
"to",
|
|
8743
11281
|
"in",
|
|
11282
|
+
"on",
|
|
11283
|
+
"with",
|
|
11284
|
+
"into",
|
|
11285
|
+
"from",
|
|
8744
11286
|
"is",
|
|
11287
|
+
"are",
|
|
11288
|
+
"be",
|
|
11289
|
+
"i",
|
|
11290
|
+
"we",
|
|
11291
|
+
"my",
|
|
11292
|
+
"it",
|
|
11293
|
+
"you",
|
|
8745
11294
|
"me",
|
|
8746
|
-
"
|
|
8747
|
-
"
|
|
8748
|
-
"
|
|
11295
|
+
"us",
|
|
11296
|
+
"your",
|
|
11297
|
+
"our",
|
|
11298
|
+
"this",
|
|
11299
|
+
"that",
|
|
11300
|
+
"these",
|
|
11301
|
+
"those",
|
|
11302
|
+
"there",
|
|
11303
|
+
"here",
|
|
11304
|
+
"some",
|
|
11305
|
+
"any",
|
|
11306
|
+
"all",
|
|
11307
|
+
"also",
|
|
8749
11308
|
"please",
|
|
8750
|
-
"
|
|
8751
|
-
"
|
|
8752
|
-
"
|
|
8753
|
-
"
|
|
11309
|
+
"about",
|
|
11310
|
+
"how",
|
|
11311
|
+
"what",
|
|
11312
|
+
"which",
|
|
11313
|
+
"where",
|
|
11314
|
+
"when",
|
|
11315
|
+
"who",
|
|
11316
|
+
"why",
|
|
11317
|
+
"find",
|
|
11318
|
+
"give",
|
|
11319
|
+
"show",
|
|
11320
|
+
"need",
|
|
11321
|
+
"want",
|
|
11322
|
+
"looking",
|
|
11323
|
+
"look",
|
|
11324
|
+
"help",
|
|
11325
|
+
"do",
|
|
11326
|
+
"does",
|
|
11327
|
+
"did",
|
|
11328
|
+
"can",
|
|
11329
|
+
"could",
|
|
11330
|
+
"should",
|
|
11331
|
+
"would",
|
|
11332
|
+
"will",
|
|
11333
|
+
"handle",
|
|
11334
|
+
"handles",
|
|
11335
|
+
"handling",
|
|
11336
|
+
"aiwg",
|
|
11337
|
+
"skill",
|
|
11338
|
+
"skills",
|
|
11339
|
+
"agent",
|
|
11340
|
+
"agents",
|
|
11341
|
+
"command",
|
|
11342
|
+
"commands",
|
|
11343
|
+
"rule",
|
|
11344
|
+
"rules",
|
|
11345
|
+
"flow",
|
|
11346
|
+
"flows",
|
|
11347
|
+
"workflow",
|
|
11348
|
+
"workflows"
|
|
8754
11349
|
]);
|
|
8755
|
-
function
|
|
8756
|
-
return value.toLowerCase().replace(/[_
|
|
8757
|
-
}
|
|
8758
|
-
function canonicalDiscoveryName(value) {
|
|
8759
|
-
return normalizeDiscoveryText(value).replace(/[\s.-]+/g, "");
|
|
11350
|
+
function normalizeDiscoveryName(value) {
|
|
11351
|
+
return value.toLowerCase().replace(/[-_\s]+/g, " ").trim();
|
|
8760
11352
|
}
|
|
8761
11353
|
function discoveryTokens(value) {
|
|
8762
|
-
return
|
|
11354
|
+
return value.toLowerCase().split(/[^a-z0-9-]+/).filter((token) => token.length > 1 && !DISCOVERY_STOPWORDS.has(token));
|
|
8763
11355
|
}
|
|
8764
11356
|
function facetValues(item, names) {
|
|
8765
11357
|
return names.flatMap((name) => item.facets[name] ?? []);
|
|
@@ -8769,17 +11361,58 @@ function addDiscoveryMatch(matches, match) {
|
|
|
8769
11361
|
matches.push(match);
|
|
8770
11362
|
}
|
|
8771
11363
|
}
|
|
8772
|
-
function
|
|
8773
|
-
if (
|
|
8774
|
-
|
|
8775
|
-
|
|
8776
|
-
|
|
11364
|
+
function damerauLevenshteinAtMostOne(left, right) {
|
|
11365
|
+
if (left === right) return true;
|
|
11366
|
+
if (Math.abs(left.length - right.length) > 1) return false;
|
|
11367
|
+
if (left.length === right.length) {
|
|
11368
|
+
let firstDiff = -1;
|
|
11369
|
+
let diffCount = 0;
|
|
11370
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
11371
|
+
if (left[index] !== right[index]) {
|
|
11372
|
+
if (firstDiff < 0) firstDiff = index;
|
|
11373
|
+
diffCount += 1;
|
|
11374
|
+
}
|
|
11375
|
+
}
|
|
11376
|
+
if (diffCount === 1) return true;
|
|
11377
|
+
return diffCount === 2 && firstDiff + 1 < left.length && left[firstDiff] === right[firstDiff + 1] && left[firstDiff + 1] === right[firstDiff];
|
|
11378
|
+
}
|
|
11379
|
+
const shorter = left.length < right.length ? left : right;
|
|
11380
|
+
const longer = left.length < right.length ? right : left;
|
|
11381
|
+
let shorterIndex = 0;
|
|
11382
|
+
let longerIndex = 0;
|
|
11383
|
+
let edits = 0;
|
|
11384
|
+
while (shorterIndex < shorter.length && longerIndex < longer.length) {
|
|
11385
|
+
if (shorter[shorterIndex] === longer[longerIndex]) {
|
|
11386
|
+
shorterIndex += 1;
|
|
11387
|
+
longerIndex += 1;
|
|
11388
|
+
} else {
|
|
11389
|
+
edits += 1;
|
|
11390
|
+
if (edits > 1) return false;
|
|
11391
|
+
longerIndex += 1;
|
|
11392
|
+
}
|
|
11393
|
+
}
|
|
11394
|
+
return true;
|
|
8777
11395
|
}
|
|
8778
|
-
function
|
|
11396
|
+
function nearDiscoveryNameMatch(query, name) {
|
|
11397
|
+
const queryParts = normalizeDiscoveryName(query).split(/\s+/).filter(Boolean);
|
|
11398
|
+
const nameParts = normalizeDiscoveryName(name).split(/\s+/).filter(Boolean);
|
|
11399
|
+
if (queryParts.length !== nameParts.length) return false;
|
|
11400
|
+
return queryParts.every((part, index) => {
|
|
11401
|
+
const target = nameParts[index];
|
|
11402
|
+
if (part === target) return true;
|
|
11403
|
+
if (part.length < 5 || target.length < 5) return false;
|
|
11404
|
+
return damerauLevenshteinAtMostOne(part, target);
|
|
11405
|
+
});
|
|
11406
|
+
}
|
|
11407
|
+
function lowerValues(values) {
|
|
11408
|
+
return values.map((value) => value.toLowerCase());
|
|
11409
|
+
}
|
|
11410
|
+
function discoveryMatches(item, query, options = {}) {
|
|
8779
11411
|
if (!query) return [];
|
|
8780
11412
|
const matches = [];
|
|
8781
11413
|
const tokens = discoveryTokens(query);
|
|
8782
|
-
const
|
|
11414
|
+
const lower = tokens.length > 0 ? tokens.join(" ") : query.toLowerCase().trim();
|
|
11415
|
+
const rawLower = query.toLowerCase().trim();
|
|
8783
11416
|
const idParts = item.id.split(/[:/]/);
|
|
8784
11417
|
const names = [
|
|
8785
11418
|
item.id,
|
|
@@ -8805,33 +11438,66 @@ function discoveryMatches(item, query) {
|
|
|
8805
11438
|
...item.tags
|
|
8806
11439
|
].filter((value) => hasString(value));
|
|
8807
11440
|
const sourceValues = [item.source?.path, item.source?.repo_relative_path, item.source?.locator].filter((value) => hasString(value));
|
|
11441
|
+
const title = recordTitle(item);
|
|
11442
|
+
const summary = [item.search?.summary, recordText(item)].filter((value) => hasString(value)).join("\n");
|
|
11443
|
+
const type = item.search?.type ?? item.type;
|
|
11444
|
+
const tags = [...item.search?.tags ?? [], ...item.tags];
|
|
11445
|
+
const useMultiToken = tokens.length > 1;
|
|
11446
|
+
const minHits = useMultiToken ? options.relaxOverlap ? 1 : Math.ceil(tokens.length / 2) : 1;
|
|
11447
|
+
const overlapOK = (hits) => useMultiToken && hits >= minHits;
|
|
11448
|
+
const addInfo = (match) => addDiscoveryMatch(matches, { ...match, score: 0 });
|
|
11449
|
+
const addScore = (score2) => {
|
|
11450
|
+
if (score2 > 0) addDiscoveryMatch(matches, { field: "id", value: item.id, score: score2, reason: "aiwg discovery score" });
|
|
11451
|
+
};
|
|
8808
11452
|
for (const name of names) {
|
|
8809
|
-
|
|
8810
|
-
|
|
8811
|
-
|
|
8812
|
-
|
|
8813
|
-
}
|
|
8814
|
-
|
|
11453
|
+
if (normalizeDiscoveryName(query) === normalizeDiscoveryName(name)) {
|
|
11454
|
+
addInfo({ field: "id", value: name, reason: "exact canonical name" });
|
|
11455
|
+
addScore(1.001);
|
|
11456
|
+
return matches;
|
|
11457
|
+
}
|
|
11458
|
+
if (nearDiscoveryNameMatch(query, name)) {
|
|
11459
|
+
addInfo({ field: "id", value: name, reason: "near canonical name" });
|
|
11460
|
+
addScore(0.951);
|
|
11461
|
+
return matches;
|
|
8815
11462
|
}
|
|
8816
11463
|
}
|
|
8817
|
-
|
|
8818
|
-
const
|
|
8819
|
-
|
|
8820
|
-
|
|
8821
|
-
|
|
8822
|
-
|
|
11464
|
+
let score = 0;
|
|
11465
|
+
const scoreText = (field, value, reason, exactScore, overlapScore, weight, exactBonus = 0) => {
|
|
11466
|
+
const normalized = value.toLowerCase();
|
|
11467
|
+
if (normalized.includes(lower)) {
|
|
11468
|
+
score += exactScore * weight;
|
|
11469
|
+
if (normalized === lower) score += exactBonus;
|
|
11470
|
+
addInfo({ field, value, reason });
|
|
11471
|
+
} else if (useMultiToken) {
|
|
11472
|
+
const hits = tokens.filter((token) => normalized.includes(token)).length;
|
|
11473
|
+
if (overlapOK(hits)) {
|
|
11474
|
+
score += overlapScore * weight * (hits / tokens.length);
|
|
11475
|
+
addInfo({ field, value, reason });
|
|
11476
|
+
}
|
|
11477
|
+
}
|
|
11478
|
+
};
|
|
11479
|
+
for (const trigger of lowerValues(triggers)) {
|
|
11480
|
+
if (trigger === lower || trigger === rawLower) {
|
|
11481
|
+
addInfo({ field: "facet", value: trigger, reason: "trigger phrase" });
|
|
11482
|
+
addScore(1.0008);
|
|
11483
|
+
return matches;
|
|
11484
|
+
}
|
|
8823
11485
|
}
|
|
8824
|
-
for (const
|
|
8825
|
-
|
|
8826
|
-
if (overlap > 0) addDiscoveryMatch(matches, { field: "concept", value: capability, score: 22 * overlap, reason: "capability overlap" });
|
|
11486
|
+
for (const trigger of triggers) {
|
|
11487
|
+
scoreText("facet", trigger, "trigger phrase", 0.25, 0.06, 4);
|
|
8827
11488
|
}
|
|
8828
|
-
const
|
|
8829
|
-
|
|
8830
|
-
|
|
11489
|
+
for (const capability of capabilities) scoreText("concept", capability, "capability overlap", 0.2, 0.1, 2);
|
|
11490
|
+
scoreText("title", title, "title token overlap", 0.3, 0.08, 3, 0.2);
|
|
11491
|
+
for (const tag of tags) scoreText("tag", tag, "tag token overlap", 0.2, 0.05, 2);
|
|
11492
|
+
if (summary) scoreText("text", summary, "body token overlap", 0.15, 0.04, 1);
|
|
8831
11493
|
for (const source of sourceValues) {
|
|
8832
|
-
|
|
8833
|
-
if (overlap > 0) addDiscoveryMatch(matches, { field: "source", value: source, score: 2 * overlap, reason: "path overlap" });
|
|
11494
|
+
scoreText("source", source, "path overlap", 0.1, 0.03, 1);
|
|
8834
11495
|
}
|
|
11496
|
+
if (type.toLowerCase().includes(lower)) {
|
|
11497
|
+
score += 0.1;
|
|
11498
|
+
addInfo({ field: "facet", value: type, reason: "type overlap" });
|
|
11499
|
+
}
|
|
11500
|
+
addScore(Math.min(score, 1));
|
|
8835
11501
|
return matches;
|
|
8836
11502
|
}
|
|
8837
11503
|
function rankMatches(matches, weights) {
|
|
@@ -8857,13 +11523,13 @@ function createSnippet(item, matches, q, maxLength) {
|
|
|
8857
11523
|
const firstMatch = textMatch ?? titleMatch ?? matches[0];
|
|
8858
11524
|
return clipSnippet(firstMatch?.value ?? recordText(item), q, maxLength);
|
|
8859
11525
|
}
|
|
8860
|
-
function createRankedEntries(items, q, options, ordinalBase = 0) {
|
|
11526
|
+
function createRankedEntries(items, q, options, ordinalBase = 0, discoveryOptions = {}) {
|
|
8861
11527
|
const weights = { ...DEFAULT_QUERY_WEIGHTS, ...options.weights };
|
|
8862
11528
|
const profile = options.searchProfile ?? "default";
|
|
8863
11529
|
return items.map((item, ordinal) => ({
|
|
8864
11530
|
item,
|
|
8865
11531
|
ordinal: ordinalBase + ordinal,
|
|
8866
|
-
matches: profile === "aiwg-discovery" ? discoveryMatches(item, q) : queryMatches(item, q)
|
|
11532
|
+
matches: profile === "aiwg-discovery" ? discoveryMatches(item, q, discoveryOptions) : queryMatches(item, q)
|
|
8867
11533
|
})).filter(({ item, matches }) => {
|
|
8868
11534
|
if (q && matches.length === 0) return false;
|
|
8869
11535
|
if (options.types && !options.types.includes(item.type)) return false;
|
|
@@ -8913,8 +11579,7 @@ function queryAiwgFortemiIndex(index, query = "", options = {}) {
|
|
|
8913
11579
|
const q = query.trim().toLowerCase();
|
|
8914
11580
|
const entries = createRankedEntries(index.items, q, options);
|
|
8915
11581
|
if (entries.length === 0 && q && options.searchProfile === "aiwg-discovery") {
|
|
8916
|
-
|
|
8917
|
-
return createQueryResultFromRankedEntries(createRankedEntries(index.items, relaxed, options), relaxed, options);
|
|
11582
|
+
return createQueryResultFromRankedEntries(createRankedEntries(index.items, q, options, 0, { relaxOverlap: true }), q, options);
|
|
8918
11583
|
}
|
|
8919
11584
|
return createQueryResultFromRankedEntries(entries, q, options);
|
|
8920
11585
|
}
|
|
@@ -8935,7 +11600,8 @@ function cosineSimilarity2(left, right) {
|
|
|
8935
11600
|
}
|
|
8936
11601
|
function validateAiwgStaticEmbeddingSet(value) {
|
|
8937
11602
|
const errors = [];
|
|
8938
|
-
const data = value;
|
|
11603
|
+
const data = isPlainRecord(value) ? value : {};
|
|
11604
|
+
if (!isPlainRecord(value)) errors.push("embedding set must be an object");
|
|
8939
11605
|
if (data?.schema_version !== "aiwg.fortemi.embedding.set.v1") errors.push("schema_version must be aiwg.fortemi.embedding.set.v1");
|
|
8940
11606
|
if (!hasString(data?.id)) errors.push("id is required");
|
|
8941
11607
|
if (!hasString(data?.model)) errors.push("model is required");
|
|
@@ -8943,7 +11609,12 @@ function validateAiwgStaticEmbeddingSet(value) {
|
|
|
8943
11609
|
if (!hasString(data?.generated_at)) errors.push("generated_at is required");
|
|
8944
11610
|
if (!hasString(data?.granularity)) errors.push("granularity is required");
|
|
8945
11611
|
if (!Array.isArray(data?.embeddings)) errors.push("embeddings must be an array");
|
|
8946
|
-
|
|
11612
|
+
const embeddings = Array.isArray(data.embeddings) ? data.embeddings : [];
|
|
11613
|
+
for (const [index, embedding] of embeddings.entries()) {
|
|
11614
|
+
if (!isPlainRecord(embedding)) {
|
|
11615
|
+
errors.push("embeddings[" + index + "] must be an object");
|
|
11616
|
+
continue;
|
|
11617
|
+
}
|
|
8947
11618
|
if (!hasString(embedding.record_id)) errors.push("embeddings[" + index + "].record_id is required");
|
|
8948
11619
|
if (!hasString(embedding.input_hash)) errors.push("embeddings[" + index + "].input_hash is required");
|
|
8949
11620
|
if (!Array.isArray(embedding.embedding)) errors.push("embeddings[" + index + "].embedding must be an array");
|
|
@@ -8973,7 +11644,10 @@ function queryAiwgSemanticIndex(index, embeddingSet, queryEmbedding, options = {
|
|
|
8973
11644
|
}).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);
|
|
8974
11645
|
}
|
|
8975
11646
|
function queryAiwgHybridIndex(index, embeddingSet, query, queryEmbedding, options = {}) {
|
|
8976
|
-
const
|
|
11647
|
+
const lexicalOptions = { ...options };
|
|
11648
|
+
delete lexicalOptions.limit;
|
|
11649
|
+
delete lexicalOptions.offset;
|
|
11650
|
+
const lexical = queryAiwgFortemiIndex(index, query, { ...lexicalOptions, rank: true });
|
|
8977
11651
|
const semantic = queryAiwgSemanticIndex(index, embeddingSet, queryEmbedding, { limit: index.items.length });
|
|
8978
11652
|
const lexicalWeight = options.lexicalWeight ?? 0.5;
|
|
8979
11653
|
const semanticWeight = options.semanticWeight ?? 0.5;
|
|
@@ -8995,8 +11669,15 @@ function queryAiwgHybridIndex(index, embeddingSet, query, queryEmbedding, option
|
|
|
8995
11669
|
};
|
|
8996
11670
|
}).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);
|
|
8997
11671
|
}
|
|
8998
|
-
|
|
11672
|
+
var DEFAULT_AIWG_DUPLICATE_SCAN_MAX_EMBEDDINGS = 5e3;
|
|
11673
|
+
function findAiwgStaticDuplicatePairs(index, embeddingSet, threshold = 0.9, options) {
|
|
8999
11674
|
assertAiwgStaticEmbeddingSet(embeddingSet);
|
|
11675
|
+
const maxEmbeddings = options?.maxEmbeddings ?? DEFAULT_AIWG_DUPLICATE_SCAN_MAX_EMBEDDINGS;
|
|
11676
|
+
if (embeddingSet.embeddings.length > maxEmbeddings) {
|
|
11677
|
+
throw new Error(
|
|
11678
|
+
"Embedding set too large for duplicate scan: " + embeddingSet.embeddings.length + " > " + maxEmbeddings + " (raise options.maxEmbeddings to override for trusted input)"
|
|
11679
|
+
);
|
|
11680
|
+
}
|
|
9000
11681
|
const byId = new Map(index.items.map((item) => [item.id, item]));
|
|
9001
11682
|
const pairs = [];
|
|
9002
11683
|
for (let leftIndex = 0; leftIndex < embeddingSet.embeddings.length; leftIndex += 1) {
|
|
@@ -9036,6 +11717,7 @@ function matchSetCacheKey(q, options) {
|
|
|
9036
11717
|
concepts: options.concepts ?? null,
|
|
9037
11718
|
privacy: options.privacy ?? null,
|
|
9038
11719
|
rel: options.relationshipTargetId ?? null,
|
|
11720
|
+
searchProfile: options.searchProfile ?? "default",
|
|
9039
11721
|
weights: { ...DEFAULT_QUERY_WEIGHTS, ...options.weights }
|
|
9040
11722
|
});
|
|
9041
11723
|
}
|
|
@@ -9124,15 +11806,21 @@ function edgeFromRelationship(sourceId, relationship) {
|
|
|
9124
11806
|
}
|
|
9125
11807
|
function relationshipMatches(edge, options = {}) {
|
|
9126
11808
|
const type = relationshipTypeFilter(options);
|
|
9127
|
-
const direction = options.direction ?? "both";
|
|
9128
11809
|
if (type && edge.type !== type) return false;
|
|
9129
11810
|
if (options.relationshipDirection && edge.direction !== options.relationshipDirection) return false;
|
|
9130
11811
|
if (options.sourceId && edge.source_id !== options.sourceId) return false;
|
|
9131
11812
|
if (options.targetId && edge.target_id !== options.targetId) return false;
|
|
9132
|
-
if (
|
|
9133
|
-
if (direction === "in" && options.sourceId && edge.source_id !== options.sourceId) return false;
|
|
11813
|
+
if (options.endpointId && edge.source_id !== options.endpointId && edge.target_id !== options.endpointId) return false;
|
|
9134
11814
|
return true;
|
|
9135
11815
|
}
|
|
11816
|
+
function assertRelationshipDirectionAnchor(options) {
|
|
11817
|
+
if (options.direction === "out" && !options.sourceId) {
|
|
11818
|
+
throw new Error("relationship direction 'out' requires sourceId");
|
|
11819
|
+
}
|
|
11820
|
+
if (options.direction === "in" && !options.targetId) {
|
|
11821
|
+
throw new Error("relationship direction 'in' requires targetId");
|
|
11822
|
+
}
|
|
11823
|
+
}
|
|
9136
11824
|
function nodeSummary(item) {
|
|
9137
11825
|
return { id: item.id, type: item.type, title: recordTitle(item) };
|
|
9138
11826
|
}
|
|
@@ -9140,6 +11828,7 @@ function addNode(nodes, item) {
|
|
|
9140
11828
|
if (item) nodes.set(item.id, nodeSummary(item));
|
|
9141
11829
|
}
|
|
9142
11830
|
function relationshipResultFromRecords(records, options = {}) {
|
|
11831
|
+
assertRelationshipDirectionAnchor(options);
|
|
9143
11832
|
const byId = new Map(records.map((record) => [record.id, record]));
|
|
9144
11833
|
const edges = [];
|
|
9145
11834
|
for (const record of records) {
|
|
@@ -9149,7 +11838,8 @@ function relationshipResultFromRecords(records, options = {}) {
|
|
|
9149
11838
|
edges.push(edge);
|
|
9150
11839
|
}
|
|
9151
11840
|
}
|
|
9152
|
-
const
|
|
11841
|
+
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));
|
|
11842
|
+
const limitedEdges = options.limit ? sortedEdges.slice(0, options.limit) : sortedEdges;
|
|
9153
11843
|
const nodes = /* @__PURE__ */ new Map();
|
|
9154
11844
|
for (const edge of limitedEdges) {
|
|
9155
11845
|
addNode(nodes, byId.get(edge.source_id));
|
|
@@ -9166,7 +11856,8 @@ function neighborQueryOptions(id, options = {}) {
|
|
|
9166
11856
|
return {
|
|
9167
11857
|
...options,
|
|
9168
11858
|
...direction === "out" ? { sourceId: id } : {},
|
|
9169
|
-
...direction === "in" ? { targetId: id } : {}
|
|
11859
|
+
...direction === "in" ? { targetId: id } : {},
|
|
11860
|
+
...direction === "both" ? { endpointId: id } : {}
|
|
9170
11861
|
};
|
|
9171
11862
|
}
|
|
9172
11863
|
function filterNeighborResult(id, result, options = {}) {
|
|
@@ -9461,7 +12152,7 @@ function createAiwgIndexController(initialIndex) {
|
|
|
9461
12152
|
notify();
|
|
9462
12153
|
},
|
|
9463
12154
|
createReviewDecisionExport(generatedAt) {
|
|
9464
|
-
const source = index ?? (chunked ? { schema_version: "aiwg.fortemi.index.export.v1" } : null);
|
|
12155
|
+
const source = index ?? (chunked ? { schema_version: chunked.manifest.source_export_schema_version ?? "aiwg.fortemi.index.export.v1" } : null);
|
|
9465
12156
|
if (!source) throw new Error("No AIWG index export or chunked manifest loaded");
|
|
9466
12157
|
return createAiwgReviewDecisionExport(source, reviewDecisions, generatedAt);
|
|
9467
12158
|
},
|
|
@@ -9475,13 +12166,14 @@ function createAiwgIndexController(initialIndex) {
|
|
|
9475
12166
|
}
|
|
9476
12167
|
function aiwgFortemiIndexToCommunityGraph(index, options = {}) {
|
|
9477
12168
|
const ids = new Set(index.items.map((item) => item.id));
|
|
9478
|
-
const relationshipWeights = options.relationshipWeights ??
|
|
12169
|
+
const relationshipWeights = options.relationshipWeights ?? /* @__PURE__ */ Object.create(null);
|
|
9479
12170
|
const edgeCounts = /* @__PURE__ */ new Map();
|
|
9480
12171
|
for (const item of index.items) {
|
|
9481
12172
|
for (const relationship of item.relationships) {
|
|
9482
12173
|
if (!ids.has(relationship.target_id) && !options.includeDanglingRelationships) continue;
|
|
9483
12174
|
const kind = relationship.type;
|
|
9484
|
-
const
|
|
12175
|
+
const configuredWeight = Object.prototype.hasOwnProperty.call(relationshipWeights, kind) ? relationshipWeights[kind] : void 0;
|
|
12176
|
+
const baseWeight = typeof configuredWeight === "number" && Number.isFinite(configuredWeight) ? configuredWeight : 1;
|
|
9485
12177
|
const key = `${item.id}\0${relationship.target_id}\0${kind}`;
|
|
9486
12178
|
const existing = edgeCounts.get(key);
|
|
9487
12179
|
if (existing) existing.weight += baseWeight;
|
|
@@ -9518,8 +12210,8 @@ function communityIdsFor(item, options) {
|
|
|
9518
12210
|
}
|
|
9519
12211
|
|
|
9520
12212
|
// src/index.ts
|
|
9521
|
-
var VERSION = "2026.7.
|
|
12213
|
+
var VERSION = "2026.7.4";
|
|
9522
12214
|
|
|
9523
|
-
export { AIWG_SCAN_REQUIRED_FIELDS, ArchiveManager, AttachmentsRepository, CURRENT_MIGRATION_HEAD, CURRENT_SHARD_VERSION, CapabilityManager, CaptureKnowledgeInputSchema, CollectionsRepository, CommunitiesRepository, DB_SNAPSHOT_SCHEMA_VERSION, DbSnapshotVersionError, EMBED_REQUEST_KIND, EMBED_RESPONSE_KIND, EmbeddingSetsRepository, FallbackRouter, FortemiToolManifest, GetNoteInputSchema, GraphRepository, JOB_CAPABILITIES, JOB_PRIORITIES, JobQueueWorker, LOCAL_ENDPOINTS, LinksRepository, ListNotesInputSchema, ManageArchiveInputSchema, ManageAttachmentsInputSchema, ManageCapabilitiesInputSchema, ManageCollectionsInputSchema, ManageLinksInputSchema, ManageNoteInputSchema, ManageTagsInputSchema, MemoryBlobStore, MigrationRunner, NotesRepository, OpenAICompatibleProvider, PGliteStorageBackend, PGliteStorageBackendFactory, PGliteWorkerClient, PGliteWorkerStorageBackend, PGliteWorkerStorageBackendFactory, ProvenanceRepository, ProviderRegistry, SHARD_FORMAT, SUPPORTED_PGLITE_VERSION, SearchInputSchema, SearchRepository, SkosRepository, TagsRepository, TransactionProxy, TypedEventBus, VERSION, aiRevisionHandler, aiwgFortemiIndexToCommunityGraph, allMigrations, appendPluginScript, assertAiwgFortemiChunkManifest, assertAiwgFortemiChunkPart, assertAiwgFortemiIndexExport, assertAiwgStaticEmbeddingSet, buildAiwgChunkedIndex, buildAiwgStaticEmbeddingSet, buildNoteConditions, buildPluginCsp, captureKnowledge, chunkText, classifyError, classifyModel, clearPrefetchedShard, collectionFromShard, collectionToShard, computeHash, computeSri, conceptTaggingHandler, cosineSimilarity, createAiwgFetchChunkLoader, createAiwgFetchDetailLoader, createAiwgIndexController, createAiwgReviewDecisionExport, createBlobStore, createCosineSemanticProvider, createCspReportHandler, createFortemi, createLegacyProvider, createPGliteBackend, createPGliteInstance, createRemoteBackend, createRoutes, createShardBackend, createWorkerEmbedFunction, defaultStorageBackendFactory, detectCommunities, detectGpuCapabilities, detectInferenceCapabilities, discoverLocalProviders, dumpDbSnapshot, embeddingFromShard, embeddingGenerationHandler, embeddingSetFromShard, embeddingSetMemberToShard, embeddingSetToShard, embeddingToShard, enqueueFullWorkflow, enqueueJob, enqueueNoteCreationJobs, estimateModelFit, estimateVramMB, estimateVramTier, exportShard, fetchPluginScript, findAiwgStaticDuplicatePairs, fortemiManifest, fromPrefetched, generateId, getAiwgFortemiFacets, getEmbedFunction, getFortemiBridge, getFortemiSecretStore, getJobQueueStatus, getLlmFunction, getNote, getPrefetchedSha256, handleEmbedRequests, hasFortemiSecureSecrets, importShard, isPluginScriptAllowed, isShardPrefetched, linkFromShard, linkToShard, linkingHandler, listNotes, manageArchive, manageAttachments, manageCapabilities, manageCollections, manageLinks, manageNote, manageTags, matchRoute, noteFromShard, noteSkosTagToShard, noteToShard, openShard, packTarGz, parseCspReport, prefetchShard, provenanceEdgeToShard, queryAiwgFortemiIndex, queryAiwgHybridIndex, queryAiwgSemanticIndex, registerLlmCapability, registerSemanticCapability, registerSemanticCapabilityWorker, registerServiceWorker, restoreDbSnapshot, searchTool, selectBackend, selectLlmModel, setEmbedFunction, setLlmFunction, sha256Hex, skosConceptToShard, skosRelationToShard, skosSchemeToShard, suggestTags,
|
|
12215
|
+
export { AIWG_SCAN_REQUIRED_FIELDS, ArchiveManager, AttachmentsRepository, CURRENT_MIGRATION_HEAD, CURRENT_SHARD_VERSION, CapabilityManager, CaptureKnowledgeInputSchema, CollectionsRepository, CommunitiesRepository, DB_SNAPSHOT_SCHEMA_VERSION, DbSnapshotVersionError, EMBED_REQUEST_KIND, EMBED_RESPONSE_KIND, EmbeddingSetsRepository, FORTEMI_COMPATIBILITY_PATH, FORTEMI_COMPATIBILITY_STATES, FORTEMI_REQUIRED_COMPATIBILITY_CAPABILITIES, FallbackRouter, FortemiToolManifest, GetNoteInputSchema, GraphRepository, JOB_CAPABILITIES, JOB_PRIORITIES, JobQueueWorker, LOCAL_ENDPOINTS, LinksRepository, ListNotesInputSchema, ManageArchiveInputSchema, ManageAttachmentsInputSchema, ManageCapabilitiesInputSchema, ManageCollectionsInputSchema, ManageLinksInputSchema, ManageNoteInputSchema, ManageTagsInputSchema, MemoryBlobStore, MigrationRunner, NotesRepository, OpenAICompatibleProvider, PGliteStorageBackend, PGliteStorageBackendFactory, PGliteWorkerClient, PGliteWorkerStorageBackend, PGliteWorkerStorageBackendFactory, ProvenanceRepository, ProviderRegistry, SHARD_FORMAT, SUPPORTED_PGLITE_VERSION, SearchInputSchema, SearchRepository, SkosRepository, TagsRepository, TransactionProxy, TypedEventBus, VERSION, aiRevisionHandler, aiwgFortemiIndexToCommunityGraph, allMigrations, appendPluginScript, assertAiwgFortemiChunkManifest, assertAiwgFortemiChunkPart, assertAiwgFortemiIndexExport, assertAiwgStaticEmbeddingSet, assertShardComponentRecord, buildAiwgChunkedIndex, buildAiwgStaticEmbeddingSet, buildNoteConditions, buildPluginCsp, captureKnowledge, chunkText, classifyError, classifyModel, clearPrefetchedShard, collectionFromShard, collectionToShard, computeBlobHash, computeHash, computeSri, conceptTaggingHandler, cosineSimilarity, createAiwgFetchChunkLoader, createAiwgFetchDetailLoader, createAiwgIndexController, createAiwgReviewDecisionExport, createBlobStore, createCosineSemanticProvider, createCspReportHandler, createFortemi, createLegacyProvider, createPGliteBackend, createPGliteInstance, createRemoteBackend, createRoutes, createShardBackend, createWorkerEmbedFunction, defaultStorageBackendFactory, detectCommunities, detectGpuCapabilities, detectInferenceCapabilities, discoverLocalProviders, dumpDbSnapshot, embeddingConfigToShard, embeddingFromShard, embeddingGenerationHandler, embeddingSetFromShard, embeddingSetMemberToShard, embeddingSetToShard, embeddingToShard, enqueueFullWorkflow, enqueueJob, enqueueNoteCreationJobs, estimateModelFit, estimateVramMB, estimateVramTier, exportShard, fetchAndValidateFortemiCompatibility, fetchPluginScript, filterAiwgRecordsByPrivacy, findAiwgStaticDuplicatePairs, formatFortemiCompatibilitySummary, fortemiCompatibilityUrl, fortemiManifest, fromPrefetched, generateId, getAiwgFortemiFacets, getAiwgFortemiIndexExportSchema, getEmbedFunction, getFortemiBridge, getFortemiSecretStore, getJobQueueStatus, getKnowledgeShardSchema, getLlmFunction, getNote, getPrefetchedSha256, handleEmbedRequests, hasFortemiSecureSecrets, importShard, isPluginScriptAllowed, isShardPrefetched, linkFromShard, linkToShard, linkingHandler, listNotes, manageArchive, manageAttachments, manageCapabilities, manageCollections, manageLinks, manageNote, manageTags, matchRoute, noteFromShard, noteSkosTagToShard, noteToShard, openShard, packTarGz, parseCspReport, prefetchShard, provenanceEdgeToShard, queryAiwgFortemiIndex, queryAiwgHybridIndex, queryAiwgSemanticIndex, registerLlmCapability, registerSemanticCapability, registerSemanticCapabilityWorker, registerServiceWorker, restoreDbSnapshot, searchTool, selectBackend, selectLlmModel, setEmbedFunction, setLlmFunction, sha256Hex, skosConceptToShard, skosRelationToShard, skosSchemeToShard, suggestTags, tagsToShard, templateToShard, titleGenerationHandler, unpackTarGz, unregisterLlmCapability, unregisterSemanticCapability, urlLinkToShard, validateAiwgFortemiChunkManifest, validateAiwgFortemiChunkPart, validateAiwgFortemiIndexExport, validateAiwgFortemiIndexExportSchema, validateAiwgFortemiProjectedRecordSchema, validateAiwgStaticEmbeddingSet, validateChecksums, validateFortemiCompatibilityResponse, validateShardArchive, validateShardComponentRecord, validateShardManifest, verifyDbSnapshotMeta, verifySri };
|
|
9524
12216
|
//# sourceMappingURL=index.js.map
|
|
9525
12217
|
//# sourceMappingURL=index.js.map
|