videobook-engine 2.0.1 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/README.md +87 -41
  2. package/dist/artifact-names.d.ts +6 -0
  3. package/dist/artifact-names.d.ts.map +1 -0
  4. package/dist/artifact-names.js +56 -0
  5. package/dist/artifact-names.js.map +1 -0
  6. package/dist/artifacts.d.ts.map +1 -1
  7. package/dist/artifacts.js +57 -23
  8. package/dist/artifacts.js.map +1 -1
  9. package/dist/books.js +2 -1
  10. package/dist/books.js.map +1 -1
  11. package/dist/cas.d.ts +27 -1
  12. package/dist/cas.d.ts.map +1 -1
  13. package/dist/cas.js +78 -6
  14. package/dist/cas.js.map +1 -1
  15. package/dist/catalog-metadata.d.ts +56 -0
  16. package/dist/catalog-metadata.d.ts.map +1 -0
  17. package/dist/catalog-metadata.js +42 -0
  18. package/dist/catalog-metadata.js.map +1 -0
  19. package/dist/communications.js +4 -2
  20. package/dist/communications.js.map +1 -1
  21. package/dist/context.d.ts +6 -1
  22. package/dist/context.d.ts.map +1 -1
  23. package/dist/context.js +35 -6
  24. package/dist/context.js.map +1 -1
  25. package/dist/domain.d.ts +4 -0
  26. package/dist/domain.d.ts.map +1 -1
  27. package/dist/domain.js +870 -58
  28. package/dist/domain.js.map +1 -1
  29. package/dist/edit-policy.d.ts +3 -0
  30. package/dist/edit-policy.d.ts.map +1 -0
  31. package/dist/edit-policy.js +6 -0
  32. package/dist/edit-policy.js.map +1 -0
  33. package/dist/edits.d.ts +10 -0
  34. package/dist/edits.d.ts.map +1 -0
  35. package/dist/edits.js +1392 -0
  36. package/dist/edits.js.map +1 -0
  37. package/dist/engine-types.d.ts +110 -60
  38. package/dist/engine-types.d.ts.map +1 -1
  39. package/dist/engine-types.js +33 -0
  40. package/dist/engine-types.js.map +1 -1
  41. package/dist/engine.d.ts +69 -8
  42. package/dist/engine.d.ts.map +1 -1
  43. package/dist/engine.js +24 -18
  44. package/dist/engine.js.map +1 -1
  45. package/dist/files.d.ts.map +1 -1
  46. package/dist/files.js +31 -27
  47. package/dist/files.js.map +1 -1
  48. package/dist/fork.d.ts +147 -0
  49. package/dist/fork.d.ts.map +1 -0
  50. package/dist/fork.js +609 -0
  51. package/dist/fork.js.map +1 -0
  52. package/dist/history.d.ts +0 -4
  53. package/dist/history.d.ts.map +1 -1
  54. package/dist/history.js +166 -595
  55. package/dist/history.js.map +1 -1
  56. package/dist/index.d.ts +16 -4
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +12 -2
  59. package/dist/index.js.map +1 -1
  60. package/dist/job-queue.d.ts +2 -0
  61. package/dist/job-queue.d.ts.map +1 -1
  62. package/dist/job-queue.js +29 -2
  63. package/dist/job-queue.js.map +1 -1
  64. package/dist/media.d.ts.map +1 -1
  65. package/dist/media.js +1 -0
  66. package/dist/media.js.map +1 -1
  67. package/dist/merge-policy.d.ts +99 -0
  68. package/dist/merge-policy.d.ts.map +1 -0
  69. package/dist/merge-policy.js +310 -0
  70. package/dist/merge-policy.js.map +1 -0
  71. package/dist/metadata.d.ts.map +1 -1
  72. package/dist/metadata.js +15 -20
  73. package/dist/metadata.js.map +1 -1
  74. package/dist/migration.d.ts +9 -0
  75. package/dist/migration.d.ts.map +1 -0
  76. package/dist/migration.js +534 -0
  77. package/dist/migration.js.map +1 -0
  78. package/dist/mvp-contract-fixtures.d.ts +244 -0
  79. package/dist/mvp-contract-fixtures.d.ts.map +1 -0
  80. package/dist/mvp-contract-fixtures.js +556 -0
  81. package/dist/mvp-contract-fixtures.js.map +1 -0
  82. package/dist/mvp-contracts.d.ts +761 -0
  83. package/dist/mvp-contracts.d.ts.map +1 -0
  84. package/dist/mvp-contracts.js +59 -0
  85. package/dist/mvp-contracts.js.map +1 -0
  86. package/dist/mvp-time.d.ts +54 -0
  87. package/dist/mvp-time.d.ts.map +1 -0
  88. package/dist/mvp-time.js +148 -0
  89. package/dist/mvp-time.js.map +1 -0
  90. package/dist/notebook/types.d.ts +113 -8
  91. package/dist/notebook/types.d.ts.map +1 -1
  92. package/dist/order-keys.d.ts +22 -0
  93. package/dist/order-keys.d.ts.map +1 -0
  94. package/dist/order-keys.js +262 -0
  95. package/dist/order-keys.js.map +1 -0
  96. package/dist/runtime-services.js +4 -2
  97. package/dist/runtime-services.js.map +1 -1
  98. package/dist/schema.d.ts +24 -5
  99. package/dist/schema.d.ts.map +1 -1
  100. package/dist/schema.js +644 -132
  101. package/dist/schema.js.map +1 -1
  102. package/dist/search-benchmark.d.ts +4 -0
  103. package/dist/search-benchmark.d.ts.map +1 -0
  104. package/dist/search-benchmark.js +144 -0
  105. package/dist/search-benchmark.js.map +1 -0
  106. package/dist/sequences.d.ts +20 -0
  107. package/dist/sequences.d.ts.map +1 -0
  108. package/dist/sequences.js +825 -0
  109. package/dist/sequences.js.map +1 -0
  110. package/dist/similarity.js +1 -1
  111. package/dist/storage.d.ts +3 -1
  112. package/dist/storage.d.ts.map +1 -1
  113. package/dist/storage.js +388 -7
  114. package/dist/storage.js.map +1 -1
  115. package/dist/store.d.ts +63 -2
  116. package/dist/store.d.ts.map +1 -1
  117. package/dist/store.js +351 -65
  118. package/dist/store.js.map +1 -1
  119. package/dist/streams.d.ts +10 -0
  120. package/dist/streams.d.ts.map +1 -0
  121. package/dist/streams.js +183 -0
  122. package/dist/streams.js.map +1 -0
  123. package/dist/temporal-models.d.ts +48 -0
  124. package/dist/temporal-models.d.ts.map +1 -0
  125. package/dist/temporal-models.js +278 -0
  126. package/dist/temporal-models.js.map +1 -0
  127. package/dist/temporal-search.d.ts +29 -0
  128. package/dist/temporal-search.d.ts.map +1 -0
  129. package/dist/temporal-search.js +1507 -0
  130. package/dist/temporal-search.js.map +1 -0
  131. package/dist/transcripts.d.ts +16 -0
  132. package/dist/transcripts.d.ts.map +1 -0
  133. package/dist/transcripts.js +519 -0
  134. package/dist/transcripts.js.map +1 -0
  135. package/docs/engine-layout.md +346 -106
  136. package/docs/mvp-prd.md +918 -0
  137. package/fixtures/media/vancat.mp4 +0 -0
  138. package/fixtures/media/vancat_profile.jpg +0 -0
  139. package/fixtures/v5/contract-fixtures.json +1532 -0
  140. package/package.json +30 -5
  141. package/dist/history-types.d.ts +0 -74
  142. package/dist/history-types.d.ts.map +0 -1
  143. package/dist/history-types.js +0 -2
  144. package/dist/history-types.js.map +0 -1
  145. package/dist/timeline.d.ts +0 -9
  146. package/dist/timeline.d.ts.map +0 -1
  147. package/dist/timeline.js +0 -236
  148. package/dist/timeline.js.map +0 -1
@@ -0,0 +1,1507 @@
1
+ import { createHash } from "node:crypto";
2
+ import { normalizeSourcePoint, normalizeSourceRange, rationalEquals, sourceRangeEndTick, } from "./mvp-time.js";
3
+ import { resultOf, syncResultOf } from "./context.js";
4
+ import { canonicalJson, EngineFault, parseJson } from "./store.js";
5
+ const INDEX_PHASES = [
6
+ "probe",
7
+ "segment",
8
+ "transcript",
9
+ "ocr",
10
+ "visual",
11
+ "audio",
12
+ "lexical",
13
+ "activate",
14
+ ];
15
+ export function createTemporalSearchApi(context) {
16
+ const providers = new Map();
17
+ return {
18
+ providers: {
19
+ register: (provider) => {
20
+ requiredText(provider.manifestId, "Provider manifest ID");
21
+ providers.set(provider.manifestId, provider);
22
+ },
23
+ list: () => [...providers.keys()].sort(),
24
+ },
25
+ manifests: {
26
+ register: (manifest) => syncResultOf(() => registerManifest(context, manifest)),
27
+ list: () => listManifests(context),
28
+ get: (manifestId) => syncResultOf(() => requiredManifest(context, manifestId)),
29
+ },
30
+ plan: (artifact, objectHash, manifestId, generation) => syncResultOf(() => indexPlan(context, artifact, objectHash, manifestId, generation)),
31
+ commitBatch: (input) => syncResultOf(() => commitIndexBatch(context, input)),
32
+ activate: (manifestId, generation) => syncResultOf(() => activateGeneration(context, manifestId, generation)),
33
+ coverage: (artifact) => syncResultOf(() => searchCoverage(context, artifact)),
34
+ query: (query) => queryTemporalIndex(context, providers, query),
35
+ queryPrepared: (query, reference, options = {}) => queryTemporalIndex(context, providers, query, { reference, options }),
36
+ invalidate: (artifact, objectHash) => syncResultOf(() => invalidateCoverage(context, artifact, objectHash)),
37
+ cleanup: () => syncResultOf(() => cleanupRetired(context)),
38
+ stats: () => temporalStats(context),
39
+ };
40
+ }
41
+ function registerManifest(context, manifest) {
42
+ validateManifest(manifest);
43
+ const existing = context.store.db
44
+ .prepare("SELECT * FROM runtime_index_manifests WHERE manifest_id=?")
45
+ .get(manifest.manifestId);
46
+ if (existing) {
47
+ const projected = manifestFromRow(existing);
48
+ if (canonicalJson(projected) !== canonicalJson(manifest)) {
49
+ throw new EngineFault({
50
+ code: "MANIFEST_INCOMPATIBLE",
51
+ message: `Index manifest is immutable: ${manifest.manifestId}`,
52
+ });
53
+ }
54
+ return projected;
55
+ }
56
+ context.store.runtime(() => {
57
+ context.store.db
58
+ .prepare(`INSERT INTO runtime_index_manifests(
59
+ manifest_id, provider, model_id, model_revision, license,
60
+ embedding_space, dimensions, modalities_json,
61
+ supported_languages_json, preprocessing_version,
62
+ extractor_version, created_at
63
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
64
+ .run(manifest.manifestId, manifest.provider, manifest.modelId, manifest.modelRevision, manifest.license ?? null, manifest.embeddingSpace, manifest.dimensions, canonicalJson(manifest.modalities), canonicalJson(manifest.supportedLanguages), manifest.preprocessingVersion, manifest.extractorVersion, manifest.createdAt);
65
+ });
66
+ return manifest;
67
+ }
68
+ function validateManifest(manifest) {
69
+ requiredText(manifest.manifestId, "Manifest ID");
70
+ requiredText(manifest.provider, "Manifest provider");
71
+ requiredText(manifest.modelId, "Manifest model ID");
72
+ requiredText(manifest.modelRevision, "Manifest model revision");
73
+ requiredText(manifest.embeddingSpace, "Manifest embedding space");
74
+ safeIntegerAtLeast(manifest.dimensions, 1, "Manifest dimensions");
75
+ if (manifest.modalities.length === 0) {
76
+ throw new Error("Manifest must support at least one modality");
77
+ }
78
+ requiredText(manifest.preprocessingVersion, "Preprocessing version");
79
+ requiredText(manifest.extractorVersion, "Extractor version");
80
+ safeIntegerAtLeast(manifest.createdAt, 0, "Manifest createdAt");
81
+ }
82
+ function listManifests(context) {
83
+ return context.store.db
84
+ .prepare(`SELECT * FROM runtime_index_manifests
85
+ ORDER BY created_at, manifest_id`)
86
+ .all().map(manifestFromRow);
87
+ }
88
+ function requiredManifest(context, manifestId) {
89
+ const row = context.store.db
90
+ .prepare("SELECT * FROM runtime_index_manifests WHERE manifest_id=?")
91
+ .get(manifestId);
92
+ if (!row) {
93
+ throw new EngineFault({
94
+ code: "NOT_FOUND",
95
+ message: `Index manifest not found: ${manifestId}`,
96
+ });
97
+ }
98
+ return manifestFromRow(row);
99
+ }
100
+ function manifestFromRow(row) {
101
+ return {
102
+ manifestId: row.manifest_id,
103
+ provider: row.provider,
104
+ modelId: row.model_id,
105
+ modelRevision: row.model_revision,
106
+ ...(row.license ? { license: row.license } : {}),
107
+ embeddingSpace: row.embedding_space,
108
+ dimensions: row.dimensions,
109
+ modalities: parseJson(row.modalities_json, []),
110
+ supportedLanguages: parseJson(row.supported_languages_json, []),
111
+ preprocessingVersion: row.preprocessing_version,
112
+ extractorVersion: row.extractor_version,
113
+ createdAt: row.created_at,
114
+ };
115
+ }
116
+ function indexPlan(context, artifactReference, objectHash, manifestId, generation) {
117
+ const artifact = context.artifactRow(artifactReference);
118
+ assertArtifactObject(context, artifact.artifact_id, objectHash);
119
+ requiredManifest(context, manifestId);
120
+ requiredText(generation, "Index generation");
121
+ const coverage = coverageRows(context, "WHERE artifact_id=? AND object_hash=? AND manifest_id=? AND generation=?", [artifact.artifact_id, objectHash, manifestId, generation]);
122
+ const completed = new Set(coverage
123
+ .filter((item) => item.state === "ready")
124
+ .map((item) => item.phase));
125
+ return {
126
+ artifactId: artifact.artifact_id,
127
+ objectHash,
128
+ manifestId,
129
+ generation,
130
+ phases: INDEX_PHASES,
131
+ pendingPhases: INDEX_PHASES.filter((phase) => !completed.has(phase)),
132
+ coverage,
133
+ };
134
+ }
135
+ function commitIndexBatch(context, input) {
136
+ const artifact = context.artifactRow(input.artifactId);
137
+ const manifest = requiredManifest(context, input.manifestId);
138
+ if (artifact.artifact_id !== input.artifactId) {
139
+ throw new EngineFault({
140
+ code: "INVALID_INPUT",
141
+ message: "Index batches require a canonical artifact ID",
142
+ });
143
+ }
144
+ assertArtifactObject(context, artifact.artifact_id, input.objectHash);
145
+ requiredText(input.generation, "Index generation");
146
+ safeIntegerAtLeast(input.maxUnits, 1, "Index batch max units");
147
+ if (input.observations.length > input.maxUnits) {
148
+ throw new EngineFault({
149
+ code: "RESOURCE_EXHAUSTED",
150
+ message: "Index batch exceeds maxUnits",
151
+ });
152
+ }
153
+ const batchKey = hashJson({
154
+ artifactId: input.artifactId,
155
+ objectHash: input.objectHash,
156
+ manifestId: input.manifestId,
157
+ generation: input.generation,
158
+ phase: input.phase,
159
+ cursor: input.cursor ?? null,
160
+ observations: input.observations,
161
+ });
162
+ const existing = context.store.db
163
+ .prepare("SELECT result_json FROM runtime_index_batches WHERE batch_key=?")
164
+ .get(batchKey);
165
+ if (existing) {
166
+ return parseJson(existing.result_json, {
167
+ artifactId: input.artifactId,
168
+ manifestId: input.manifestId,
169
+ phase: input.phase,
170
+ committedUnits: 0,
171
+ coveredRanges: [],
172
+ complete: false,
173
+ generation: input.generation,
174
+ });
175
+ }
176
+ const streamCache = new Map();
177
+ const observations = input.observations.map((observation, index) => normalizeObservation(context, manifest, input, observation, index, streamCache));
178
+ const coveredRanges = input.coveredRanges.map(normalizeSourceRange);
179
+ const result = {
180
+ artifactId: input.artifactId,
181
+ manifestId: input.manifestId,
182
+ phase: input.phase,
183
+ committedUnits: observations.length,
184
+ coveredRanges,
185
+ ...(input.nextCursor ? { nextCursor: input.nextCursor } : {}),
186
+ complete: input.complete,
187
+ generation: input.generation,
188
+ };
189
+ context.store.runtime((now) => {
190
+ ensureGeneration(context, manifest, input.generation, now);
191
+ const statements = prepareObservationStatements(context);
192
+ const existingSegments = existingSegmentIds(context, observations);
193
+ const writtenSegments = new Set();
194
+ for (const observation of observations) {
195
+ upsertObservation(input, observation, now, statements, existingSegments.has(observation.segmentId)
196
+ || writtenSegments.has(observation.segmentId));
197
+ writtenSegments.add(observation.segmentId);
198
+ }
199
+ const prior = context.store.db
200
+ .prepare(`SELECT indexed_units FROM runtime_index_coverage
201
+ WHERE artifact_id=? AND object_hash=? AND manifest_id=?
202
+ AND generation=? AND phase=?`)
203
+ .get(input.artifactId, input.objectHash, input.manifestId, input.generation, input.phase);
204
+ context.store.db
205
+ .prepare(`INSERT INTO runtime_index_coverage(
206
+ artifact_id, object_hash, manifest_id, generation, phase,
207
+ state, covered_ranges_json, indexed_units, total_units,
208
+ retryable, error_json, cursor, updated_at
209
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 1, NULL, ?, ?)
210
+ ON CONFLICT(
211
+ artifact_id, object_hash, manifest_id, generation, phase
212
+ ) DO UPDATE SET
213
+ state=excluded.state,
214
+ covered_ranges_json=excluded.covered_ranges_json,
215
+ indexed_units=excluded.indexed_units,
216
+ total_units=excluded.total_units,
217
+ retryable=1,
218
+ error_json=NULL,
219
+ cursor=excluded.cursor,
220
+ updated_at=excluded.updated_at`)
221
+ .run(input.artifactId, input.objectHash, input.manifestId, input.generation, input.phase, input.complete ? "ready" : "partial", canonicalJson(coveredRanges), (prior?.indexed_units ?? 0) + observations.length, input.totalUnits ?? null, input.nextCursor ?? null, now);
222
+ context.store.db
223
+ .prepare(`INSERT INTO runtime_index_batches(
224
+ batch_key, artifact_id, object_hash, manifest_id, generation,
225
+ phase, cursor, result_json, committed_at
226
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
227
+ .run(batchKey, input.artifactId, input.objectHash, input.manifestId, input.generation, input.phase, input.cursor ?? null, canonicalJson(result), now);
228
+ });
229
+ return result;
230
+ }
231
+ function normalizeObservation(context, manifest, batch, input, index, streamCache) {
232
+ if (input.artifactId !== batch.artifactId
233
+ || input.objectHash !== batch.objectHash) {
234
+ throw new EngineFault({
235
+ code: "INVALID_INPUT",
236
+ message: "Index observation source does not match its batch",
237
+ });
238
+ }
239
+ const segmentId = input.segmentId
240
+ ?? deterministicUuid(batch.generation, `${batch.artifactId}:${batch.phase}:${batch.cursor ?? "start"}:${index}`);
241
+ const range = input.range ? normalizeSourceRange(input.range) : undefined;
242
+ if (range) {
243
+ if (range.objectHash !== batch.objectHash
244
+ || (input.streamId && range.streamId !== input.streamId)) {
245
+ throw new EngineFault({
246
+ code: "INVALID_RANGE",
247
+ message: "Observation range does not match its stream identity",
248
+ });
249
+ }
250
+ let stream = streamCache.get(range.streamId);
251
+ if (!streamCache.has(range.streamId)) {
252
+ stream = context.store.db
253
+ .prepare(`SELECT artifact_id, object_hash, duration_ticks,
254
+ time_base_numerator, time_base_denominator
255
+ FROM artifact_streams WHERE stream_id=?`)
256
+ .get(range.streamId);
257
+ streamCache.set(range.streamId, stream);
258
+ }
259
+ if (!stream
260
+ || stream.artifact_id !== batch.artifactId
261
+ || stream.object_hash !== batch.objectHash
262
+ || sourceRangeEndTick(range) > stream.duration_ticks
263
+ || !rationalEquals(range.timeBase, {
264
+ numerator: stream.time_base_numerator,
265
+ denominator: stream.time_base_denominator,
266
+ })) {
267
+ throw new EngineFault({
268
+ code: "INVALID_RANGE",
269
+ message: "Observation range exceeds or mismatches its source stream",
270
+ });
271
+ }
272
+ }
273
+ else if (!input.sourcePath) {
274
+ throw new EngineFault({
275
+ code: "INVALID_INPUT",
276
+ message: "Untimed observations require a source path",
277
+ });
278
+ }
279
+ requiredText(input.segmentationVersion, "Segmentation version");
280
+ for (const text of input.texts) {
281
+ requiredText(text.text, "Segment text");
282
+ if (text.startUtf8Byte !== undefined
283
+ || text.endUtf8Byte !== undefined) {
284
+ safeIntegerAtLeast(text.startUtf8Byte ?? -1, 0, "Text start byte");
285
+ safeIntegerAtLeast(text.endUtf8Byte ?? -1, 1, "Text end byte");
286
+ if ((text.endUtf8Byte ?? 0) <= (text.startUtf8Byte ?? 0)) {
287
+ throw new EngineFault({
288
+ code: "INVALID_RANGE",
289
+ message: "Text byte range must be positive and half-open",
290
+ });
291
+ }
292
+ }
293
+ }
294
+ for (const embedding of input.embeddings) {
295
+ if (embedding.embeddingSpace !== manifest.embeddingSpace) {
296
+ throw new EngineFault({
297
+ code: "MANIFEST_INCOMPATIBLE",
298
+ message: "Observation embedding space does not match its manifest",
299
+ });
300
+ }
301
+ if (embedding.vector.length !== manifest.dimensions) {
302
+ throw new EngineFault({
303
+ code: "MANIFEST_INCOMPATIBLE",
304
+ message: "Observation vector dimensions do not match its manifest",
305
+ });
306
+ }
307
+ if (!embedding.vector.every(Number.isFinite)) {
308
+ throw new Error("Observation vectors must contain finite numbers");
309
+ }
310
+ }
311
+ return {
312
+ ...input,
313
+ segmentId,
314
+ ...(range ? { range } : {}),
315
+ };
316
+ }
317
+ function ensureGeneration(context, manifest, generation, now) {
318
+ const existing = context.store.db
319
+ .prepare(`SELECT manifest_id FROM runtime_index_generations
320
+ WHERE embedding_space=? AND generation=?`)
321
+ .get(manifest.embeddingSpace, generation);
322
+ if (existing && existing.manifest_id !== manifest.manifestId) {
323
+ throw new EngineFault({
324
+ code: "MANIFEST_INCOMPATIBLE",
325
+ message: "Generation is already bound to another manifest",
326
+ });
327
+ }
328
+ context.store.db
329
+ .prepare(`INSERT OR IGNORE INTO runtime_index_generations(
330
+ embedding_space, generation, manifest_id, state,
331
+ activated_at, created_at
332
+ ) VALUES (?, ?, ?, 'building', NULL, ?)`)
333
+ .run(manifest.embeddingSpace, generation, manifest.manifestId, now);
334
+ }
335
+ function upsertObservation(batch, observation, now, statements, replaceChildren) {
336
+ statements.upsertSegment.run(observation.segmentId, observation.artifactId, observation.streamId ?? observation.range?.streamId ?? null, observation.objectHash, observation.range ? canonicalJson(observation.range) : null, observation.sourcePath ?? null, observation.kind, observation.representativeTick ?? null, observation.segmentationVersion, batch.generation, batch.manifestId, now);
337
+ if (replaceChildren) {
338
+ statements.deleteText.run(observation.segmentId);
339
+ statements.deleteEmbeddings.run(observation.segmentId);
340
+ statements.deleteFingerprints.run(observation.segmentId);
341
+ }
342
+ observation.texts.forEach((text, index) => {
343
+ statements.insertText.run(text.textId ?? deterministicUuid(observation.segmentId, `text:${index}`), observation.segmentId, text.kind, text.language ?? null, text.text, text.startUtf8Byte ?? null, text.endUtf8Byte ?? null, text.confidence ?? null, canonicalJson(text.provenance ?? {}), batch.generation);
344
+ });
345
+ observation.embeddings.forEach((embedding, index) => {
346
+ statements.insertEmbedding.run(embedding.embeddingId
347
+ ?? deterministicUuid(observation.segmentId, `embedding:${index}`), observation.segmentId, embedding.modality, embedding.embeddingSpace, embedding.vector.length, vectorToBlob(embedding.vector), embedding.sourceHash, batch.generation, now);
348
+ });
349
+ observation.fingerprints.forEach((fingerprint, index) => {
350
+ statements.insertFingerprint.run(fingerprint.fingerprintId
351
+ ?? deterministicUuid(observation.segmentId, `fingerprint:${index}`), observation.segmentId, fingerprint.kind, fingerprint.value, fingerprint.extractorVersion, batch.generation);
352
+ });
353
+ }
354
+ function existingSegmentIds(context, observations) {
355
+ const existing = new Set();
356
+ for (let offset = 0; offset < observations.length; offset += 500) {
357
+ const ids = observations
358
+ .slice(offset, offset + 500)
359
+ .map((observation) => observation.segmentId);
360
+ if (ids.length === 0)
361
+ continue;
362
+ const rows = context.store.db
363
+ .prepare(`SELECT segment_id FROM runtime_media_segments
364
+ WHERE segment_id IN (${ids.map(() => "?").join(",")})`)
365
+ .all(...ids);
366
+ for (const row of rows)
367
+ existing.add(row.segment_id);
368
+ }
369
+ return existing;
370
+ }
371
+ function prepareObservationStatements(context) {
372
+ return {
373
+ upsertSegment: context.store.db.prepare(`INSERT INTO runtime_media_segments(
374
+ segment_id, artifact_id, stream_id, object_hash,
375
+ source_range_json, source_path, segment_kind,
376
+ representative_tick, segmentation_version,
377
+ generation, manifest_id, created_at
378
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
379
+ ON CONFLICT(segment_id) DO UPDATE SET
380
+ source_range_json=excluded.source_range_json,
381
+ source_path=excluded.source_path,
382
+ segment_kind=excluded.segment_kind,
383
+ representative_tick=excluded.representative_tick,
384
+ segmentation_version=excluded.segmentation_version`),
385
+ deleteText: context.store.db.prepare("DELETE FROM runtime_segment_text WHERE segment_id=?"),
386
+ deleteEmbeddings: context.store.db.prepare("DELETE FROM runtime_segment_embeddings WHERE segment_id=?"),
387
+ deleteFingerprints: context.store.db.prepare("DELETE FROM runtime_segment_fingerprints WHERE segment_id=?"),
388
+ insertText: context.store.db.prepare(`INSERT INTO runtime_segment_text(
389
+ text_id, segment_id, kind, language, text,
390
+ start_utf8_byte, end_utf8_byte, confidence,
391
+ provenance_json, generation
392
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
393
+ insertEmbedding: context.store.db.prepare(`INSERT INTO runtime_segment_embeddings(
394
+ embedding_id, segment_id, modality, embedding_space,
395
+ dimensions, vector_blob, source_hash, generation, updated_at
396
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`),
397
+ insertFingerprint: context.store.db.prepare(`INSERT INTO runtime_segment_fingerprints(
398
+ fingerprint_id, segment_id, kind, value,
399
+ extractor_version, generation
400
+ ) VALUES (?, ?, ?, ?, ?, ?)`),
401
+ };
402
+ }
403
+ function activateGeneration(context, manifestId, generation) {
404
+ const manifest = requiredManifest(context, manifestId);
405
+ const row = context.store.db
406
+ .prepare(`SELECT state FROM runtime_index_generations
407
+ WHERE embedding_space=? AND generation=? AND manifest_id=?`)
408
+ .get(manifest.embeddingSpace, generation, manifestId);
409
+ if (!row) {
410
+ throw new EngineFault({
411
+ code: "NOT_FOUND",
412
+ message: `Index generation not found: ${generation}`,
413
+ });
414
+ }
415
+ const ready = context.store.db
416
+ .prepare(`SELECT 1 AS present FROM runtime_index_coverage
417
+ WHERE manifest_id=? AND generation=?
418
+ AND state IN ('ready','partial') LIMIT 1`)
419
+ .get(manifestId, generation);
420
+ if (!ready) {
421
+ throw new EngineFault({
422
+ code: "INDEX_INCOMPLETE",
423
+ message: "Generation has no searchable coverage",
424
+ });
425
+ }
426
+ context.store.runtime((now) => {
427
+ context.store.db
428
+ .prepare(`UPDATE runtime_index_generations
429
+ SET state='retired'
430
+ WHERE embedding_space=? AND state='active' AND generation<>?`)
431
+ .run(manifest.embeddingSpace, generation);
432
+ context.store.db
433
+ .prepare(`UPDATE runtime_index_generations
434
+ SET state='active', activated_at=?
435
+ WHERE embedding_space=? AND generation=?`)
436
+ .run(now, manifest.embeddingSpace, generation);
437
+ });
438
+ return { manifestId, generation };
439
+ }
440
+ async function queryTemporalIndex(context, providers, query, prepared) {
441
+ return resultOf(async () => {
442
+ validateQuery(query, prepared?.reference);
443
+ if (prepared)
444
+ validatePreparedReference(prepared.reference, prepared.options);
445
+ const active = activeGenerations(context);
446
+ const generationKey = hashJson(active.map((item) => [item.embedding_space, item.generation]));
447
+ const offset = decodeCursor(query.cursor, generationKey);
448
+ const segments = querySegments(context, active, query);
449
+ const candidates = new Map(segments.map((segment) => [
450
+ segment.segment_id,
451
+ { segment, signals: [], score: 0 },
452
+ ]));
453
+ if (query.text) {
454
+ if (allowsLexical(query.modalities)) {
455
+ addLexicalSignals(context, candidates, query.text, active);
456
+ }
457
+ if (allowsSemanticVector(query.modalities)) {
458
+ await addTextVectorSignals(context, providers, candidates, query.text, active);
459
+ }
460
+ }
461
+ if (query.reference) {
462
+ addReferenceSignals(context, candidates, query.reference, active);
463
+ }
464
+ if (prepared) {
465
+ addPreparedReferenceSignals(context, candidates, prepared.reference, prepared.options, active);
466
+ }
467
+ const ranked = [...candidates.values()]
468
+ .filter((candidate) => (!query.sourceArtifactIds
469
+ || query.sourceArtifactIds.includes(candidate.segment.artifact_id))
470
+ && (!query.artifactKinds
471
+ || query.artifactKinds.includes(candidate.segment.artifact_kind)))
472
+ .map((candidate) => ({
473
+ ...candidate,
474
+ signals: candidate.signals.filter((signal) => allowsSignal(query.modalities, signal.kind)),
475
+ }))
476
+ .filter((candidate) => candidate.signals.length > 0)
477
+ .map(finalizeCandidate)
478
+ .filter((candidate) => candidate.score >= (query.minScore ?? 0))
479
+ .sort(compareCandidates);
480
+ const deduplicated = collapseOverlaps(ranked);
481
+ const limit = Math.min(query.limit ?? 20, 100);
482
+ const page = deduplicated.slice(offset, offset + limit);
483
+ return {
484
+ hits: page.map(candidateToHit),
485
+ ...(offset + limit < deduplicated.length
486
+ ? {
487
+ nextCursor: encodeCursor(generationKey, offset + limit),
488
+ }
489
+ : {}),
490
+ coverage: searchCoverage(context),
491
+ };
492
+ });
493
+ }
494
+ function allowsLexical(modalities) {
495
+ return (!modalities
496
+ || modalities.includes("auto")
497
+ || modalities.some((item) => item === "speech" || item === "ocr" || item === "metadata"));
498
+ }
499
+ function allowsSemanticVector(modalities) {
500
+ return (!modalities
501
+ || modalities.includes("auto")
502
+ || modalities.some((item) => item === "visual"
503
+ || item === "audio"
504
+ || item === "speech"
505
+ || item === "ocr"));
506
+ }
507
+ function allowsSignal(modalities, signal) {
508
+ if (!modalities || modalities.includes("auto"))
509
+ return true;
510
+ if (signal === "exact" || signal === "near")
511
+ return true;
512
+ return modalities.includes(signal);
513
+ }
514
+ function activeGenerations(context) {
515
+ return context.store.db
516
+ .prepare(`SELECT embedding_space, generation, manifest_id, state
517
+ FROM runtime_index_generations
518
+ WHERE state='active'
519
+ ORDER BY embedding_space, generation`)
520
+ .all();
521
+ }
522
+ function querySegments(context, active, query) {
523
+ if (active.length === 0)
524
+ return [];
525
+ const generations = active.map((item) => item.generation);
526
+ const placeholders = generations.map(() => "?").join(",");
527
+ const rows = context.store.db
528
+ .prepare(`SELECT s.segment_id, s.artifact_id, a.slug AS artifact_slug,
529
+ a.kind AS artifact_kind, a.created_at AS artifact_created_at,
530
+ s.stream_id, s.object_hash,
531
+ s.source_range_json, s.source_path, s.segment_kind,
532
+ s.representative_tick, s.generation, s.manifest_id
533
+ FROM runtime_media_segments s
534
+ JOIN artifacts a ON a.artifact_id=s.artifact_id
535
+ WHERE s.generation IN (${placeholders})
536
+ ORDER BY s.artifact_id, s.stream_id, s.representative_tick, s.segment_id`)
537
+ .all(...generations);
538
+ return rows.filter((row) => {
539
+ if (query.artifactKinds && !query.artifactKinds.includes(row.artifact_kind)) {
540
+ return false;
541
+ }
542
+ if (query.sourceArtifactIds
543
+ && !query.sourceArtifactIds.includes(row.artifact_id)) {
544
+ return false;
545
+ }
546
+ if (query.createdAfter !== undefined
547
+ && row.artifact_created_at <= query.createdAfter) {
548
+ return false;
549
+ }
550
+ if (query.createdBefore !== undefined
551
+ && row.artifact_created_at >= query.createdBefore) {
552
+ return false;
553
+ }
554
+ if (query.labels && !artifactHasLabels(context, row.artifact_id, query.labels)) {
555
+ return false;
556
+ }
557
+ if (query.orientations
558
+ && !query.orientations.includes(artifactOrientation(context, row.artifact_id))) {
559
+ return false;
560
+ }
561
+ if (query.indexingStates
562
+ && !query.indexingStates.includes(artifactGenerationState(context, row.artifact_id, row.generation))) {
563
+ return false;
564
+ }
565
+ if (query.durationMs) {
566
+ const range = segmentRange(row);
567
+ if (range) {
568
+ const durationMs = (range.durationTicks * range.timeBase.numerator * 1_000)
569
+ / range.timeBase.denominator;
570
+ if ((query.durationMs.min !== undefined
571
+ && durationMs < query.durationMs.min)
572
+ || (query.durationMs.max !== undefined
573
+ && durationMs > query.durationMs.max)) {
574
+ return false;
575
+ }
576
+ }
577
+ }
578
+ return true;
579
+ });
580
+ }
581
+ function artifactHasLabels(context, artifactId, requested) {
582
+ const row = context.store.db
583
+ .prepare(`SELECT value_json FROM artifact_metadata
584
+ WHERE artifact_id=? AND key='labels'`)
585
+ .get(artifactId);
586
+ const labels = row ? parseJson(row.value_json, []) : [];
587
+ return requested.every((label) => labels.includes(label));
588
+ }
589
+ function artifactOrientation(context, artifactId) {
590
+ const row = context.store.db
591
+ .prepare(`SELECT profile_json FROM artifact_streams
592
+ WHERE artifact_id=? AND kind='video'
593
+ ORDER BY stream_index LIMIT 1`)
594
+ .get(artifactId);
595
+ const profile = row
596
+ ? parseJson(row.profile_json, {})
597
+ : {};
598
+ const width = profile.video?.width ?? 1;
599
+ const height = profile.video?.height ?? 1;
600
+ if (width === height)
601
+ return "square";
602
+ return width > height ? "landscape" : "portrait";
603
+ }
604
+ function artifactGenerationState(context, artifactId, generation) {
605
+ const rows = context.store.db
606
+ .prepare(`SELECT state FROM runtime_index_coverage
607
+ WHERE artifact_id=? AND generation=?`)
608
+ .all(artifactId, generation);
609
+ return aggregateCoverageState(rows);
610
+ }
611
+ function addLexicalSignals(context, candidates, text, active) {
612
+ const generations = active.map((item) => item.generation);
613
+ if (generations.length === 0)
614
+ return;
615
+ const rows = context.store.db
616
+ .prepare(`SELECT segment_id, kind, text
617
+ FROM runtime_segment_text
618
+ WHERE generation IN (${generations.map(() => "?").join(",")})
619
+ ORDER BY segment_id, kind, text`)
620
+ .all(...generations);
621
+ const normalized = text.trim().toLocaleLowerCase();
622
+ const quoted = [...text.matchAll(/"([^"]+)"/g)]
623
+ .map((match) => match[1]?.trim().toLocaleLowerCase())
624
+ .filter((item) => Boolean(item));
625
+ const terms = normalized
626
+ .replaceAll('"', "")
627
+ .split(/\s+/)
628
+ .filter((term) => term.length > 1);
629
+ const matches = rows
630
+ .map((row) => {
631
+ const haystack = row.text.toLocaleLowerCase();
632
+ const exact = quoted.some((phrase) => haystack.includes(phrase));
633
+ const termCount = terms.filter((term) => haystack.includes(term)).length;
634
+ return {
635
+ row,
636
+ exact,
637
+ score: (exact ? 2 : 0)
638
+ + (terms.length === 0 ? 0 : termCount / terms.length),
639
+ };
640
+ })
641
+ .filter((item) => item.score > 0)
642
+ .sort((left, right) => right.score - left.score
643
+ || left.row.segment_id.localeCompare(right.row.segment_id));
644
+ matches.forEach((match, index) => {
645
+ const candidate = candidates.get(match.row.segment_id);
646
+ if (!candidate)
647
+ return;
648
+ candidate.signals.push({
649
+ kind: match.row.kind === "ocr"
650
+ ? "ocr"
651
+ : match.row.kind === "transcript"
652
+ ? "speech"
653
+ : "metadata",
654
+ rank: index + 1,
655
+ score: match.score,
656
+ explanation: match.exact
657
+ ? "Exact quoted text matched this moment"
658
+ : "Query terms matched indexed text",
659
+ });
660
+ if (!candidate.excerpt || match.exact)
661
+ candidate.excerpt = match.row.text;
662
+ });
663
+ }
664
+ async function addTextVectorSignals(context, providers, candidates, text, active) {
665
+ for (const generation of active) {
666
+ const provider = providers.get(generation.manifest_id);
667
+ if (!provider)
668
+ continue;
669
+ const manifest = requiredManifest(context, generation.manifest_id);
670
+ await provider.prepare();
671
+ const queryVector = provider.embedText(text);
672
+ const resolved = await queryVector;
673
+ if (resolved.length !== manifest.dimensions) {
674
+ throw new EngineFault({
675
+ code: "MANIFEST_INCOMPATIBLE",
676
+ message: "Query provider returned incompatible vector dimensions",
677
+ });
678
+ }
679
+ for (const modality of manifest.modalities) {
680
+ if (modality === "metadata")
681
+ continue;
682
+ rankVectorRows(context, candidates, generation, resolved, modality, `Text-to-${modality} semantic similarity`);
683
+ }
684
+ }
685
+ }
686
+ function addReferenceSignals(context, candidates, reference, active) {
687
+ for (const generation of active) {
688
+ if (reference.kind === "video") {
689
+ addOrderedVideoSignals(context, candidates, reference.range, generation);
690
+ continue;
691
+ }
692
+ const queryRows = referenceEmbeddingRows(context, reference, generation);
693
+ if (queryRows.length === 0)
694
+ continue;
695
+ const queryVector = averageVectors(queryRows.map((row) => blobToVector(row.vector_blob, row.dimensions)));
696
+ const modality = reference.kind === "audio" ? "audio" : "visual";
697
+ rankVectorRows(context, candidates, generation, queryVector, modality, `${reference.kind} reference similarity`);
698
+ addFingerprintSignals(context, candidates, queryRows, generation);
699
+ }
700
+ }
701
+ function addOrderedVideoSignals(context, candidates, rangeInput, generation) {
702
+ const range = normalizeSourceRange(rangeInput);
703
+ const query = orderedEmbeddings(context, generation, (segmentRangeValue) => segmentRangeValue.streamId === range.streamId
704
+ && segmentRangeValue.objectHash === range.objectHash
705
+ && rangesOverlap(segmentRangeValue.startTick, sourceRangeEndTick(segmentRangeValue), range.startTick, sourceRangeEndTick(range)));
706
+ if (query.length === 0)
707
+ return;
708
+ const sample = evenlySample(query, Math.min(query.length, 16));
709
+ addOrderedVideoVectorSignals(context, candidates, sample, generation);
710
+ }
711
+ function addPreparedReferenceSignals(context, candidates, reference, options, active) {
712
+ for (const generation of active) {
713
+ if (generation.embedding_space !== reference.embeddingSpace)
714
+ continue;
715
+ const manifest = requiredManifest(context, generation.manifest_id);
716
+ if (reference.kind === "image") {
717
+ const queryVector = preparedVector(reference.vector, manifest.dimensions);
718
+ rankVectorRows(context, candidates, generation, queryVector, "visual", "Prepared image reference similarity");
719
+ addPreparedFingerprintSignals(context, candidates, reference.fingerprints ?? [], generation);
720
+ continue;
721
+ }
722
+ const samples = preparedVideoSamples(reference, options);
723
+ for (const sample of samples) {
724
+ preparedVector(sample.vector, manifest.dimensions);
725
+ }
726
+ addOrderedVideoVectorSignals(context, candidates, samples.map((sample) => ({
727
+ vector: Float32Array.from(sample.vector),
728
+ })), generation);
729
+ }
730
+ }
731
+ function addOrderedVideoVectorSignals(context, candidates, sample, generation) {
732
+ if (sample.length === 0)
733
+ return;
734
+ const all = orderedEmbeddings(context, generation, () => true);
735
+ const byStream = new Map();
736
+ for (const item of all) {
737
+ const values = byStream.get(item.range.streamId) ?? [];
738
+ values.push(item);
739
+ byStream.set(item.range.streamId, values);
740
+ }
741
+ const windows = [];
742
+ for (const values of byStream.values()) {
743
+ values.sort((left, right) => left.range.startTick - right.range.startTick
744
+ || left.segment.segment_id.localeCompare(right.segment.segment_id));
745
+ if (values.length < sample.length)
746
+ continue;
747
+ for (let start = 0; start <= values.length - sample.length; start += 1) {
748
+ const window = values.slice(start, start + sample.length);
749
+ const aligned = sample.reduce((sum, item, index) => sum + cosineSimilarity(item.vector, window[index].vector), 0) / sample.length;
750
+ const coherence = transitionCoherence(sample, window);
751
+ windows.push({
752
+ values: window,
753
+ score: aligned * 0.75 + coherence * 0.25,
754
+ coherence,
755
+ });
756
+ }
757
+ }
758
+ windows
759
+ .sort((left, right) => right.score - left.score
760
+ || left.values[0].segment.segment_id.localeCompare(right.values[0].segment.segment_id))
761
+ .forEach((window, index) => {
762
+ const first = window.values[0];
763
+ const last = window.values.at(-1);
764
+ const combined = {
765
+ ...first.range,
766
+ durationTicks: sourceRangeEndTick(last.range) - first.range.startTick,
767
+ };
768
+ const segmentId = `ordered:${generation.generation}:${first.segment.segment_id}:${last.segment.segment_id}`;
769
+ const candidate = {
770
+ segment: {
771
+ ...first.segment,
772
+ segment_id: segmentId,
773
+ source_range_json: canonicalJson(combined),
774
+ representative_tick: first.range.startTick + Math.floor(combined.durationTicks / 2),
775
+ },
776
+ signals: [
777
+ {
778
+ kind: "visual",
779
+ rank: index + 1,
780
+ score: window.score,
781
+ explanation: `Ordered video alignment with temporal coherence ${window.coherence.toFixed(3)}`,
782
+ },
783
+ ],
784
+ score: 0,
785
+ };
786
+ candidates.set(segmentId, candidate);
787
+ });
788
+ }
789
+ function orderedEmbeddings(context, generation, include) {
790
+ const rows = context.store.db
791
+ .prepare(`SELECT s.segment_id, s.artifact_id, a.slug AS artifact_slug,
792
+ a.kind AS artifact_kind, a.created_at AS artifact_created_at,
793
+ s.stream_id, s.object_hash, s.source_range_json,
794
+ s.source_path, s.segment_kind, s.representative_tick,
795
+ s.generation, s.manifest_id, e.dimensions, e.vector_blob
796
+ FROM runtime_media_segments s
797
+ JOIN artifacts a ON a.artifact_id=s.artifact_id
798
+ JOIN runtime_segment_embeddings e ON e.segment_id=s.segment_id
799
+ WHERE s.generation=? AND e.embedding_space=?
800
+ AND e.modality='visual' AND s.source_range_json IS NOT NULL
801
+ ORDER BY s.stream_id, s.representative_tick, s.segment_id`)
802
+ .all(generation.generation, generation.embedding_space);
803
+ return rows.flatMap((row) => {
804
+ const range = segmentRange(row);
805
+ return range && include(range)
806
+ ? [{
807
+ segment: row,
808
+ range,
809
+ vector: blobToVector(row.vector_blob, row.dimensions),
810
+ }]
811
+ : [];
812
+ });
813
+ }
814
+ function transitionCoherence(query, candidate) {
815
+ if (query.length < 2)
816
+ return 1;
817
+ let score = 0;
818
+ for (let index = 1; index < query.length; index += 1) {
819
+ score += cosineSimilarity(vectorDifference(query[index].vector, query[index - 1].vector), vectorDifference(candidate[index].vector, candidate[index - 1].vector));
820
+ }
821
+ return score / (query.length - 1);
822
+ }
823
+ function vectorDifference(left, right) {
824
+ const result = new Float32Array(left.length);
825
+ for (let index = 0; index < left.length; index += 1) {
826
+ result[index] = left[index] - right[index];
827
+ }
828
+ return result;
829
+ }
830
+ function evenlySample(values, count) {
831
+ if (count <= 1)
832
+ return values.length === 0 ? [] : [values[0]];
833
+ if (count >= values.length)
834
+ return values;
835
+ return Array.from({ length: count }, (_, index) => {
836
+ const position = Math.round((index * (values.length - 1)) / (count - 1));
837
+ return values[position];
838
+ });
839
+ }
840
+ function rankVectorRows(context, candidates, generation, queryVector, modality, explanation) {
841
+ const rows = context.store.db
842
+ .prepare(`SELECT segment_id, embedding_space, modality, dimensions, vector_blob
843
+ FROM runtime_segment_embeddings
844
+ WHERE generation=? AND embedding_space=?
845
+ AND modality=?
846
+ ORDER BY segment_id`)
847
+ .all(generation.generation, generation.embedding_space, modality);
848
+ rows
849
+ .map((row) => ({
850
+ row,
851
+ score: cosineSimilarity(queryVector, blobToVector(row.vector_blob, row.dimensions)),
852
+ }))
853
+ .filter((item) => Number.isFinite(item.score))
854
+ .sort((left, right) => right.score - left.score
855
+ || left.row.segment_id.localeCompare(right.row.segment_id))
856
+ .forEach((item, index) => {
857
+ const candidate = candidates.get(item.row.segment_id);
858
+ if (!candidate)
859
+ return;
860
+ candidate.signals.push({
861
+ kind: modality,
862
+ rank: index + 1,
863
+ score: item.score,
864
+ explanation,
865
+ });
866
+ });
867
+ }
868
+ function referenceEmbeddingRows(context, reference, generation) {
869
+ const segments = referenceSegments(context, reference, generation.generation);
870
+ if (segments.length === 0)
871
+ return [];
872
+ const placeholders = segments.map(() => "?").join(",");
873
+ return context.store.db
874
+ .prepare(`SELECT segment_id, embedding_space, modality, dimensions, vector_blob
875
+ FROM runtime_segment_embeddings
876
+ WHERE segment_id IN (${placeholders})
877
+ AND embedding_space=?
878
+ ORDER BY segment_id`)
879
+ .all(...segments, generation.embedding_space);
880
+ }
881
+ function referenceSegments(context, reference, generation) {
882
+ if (reference.kind === "image") {
883
+ const artifact = context.artifactRow(reference.artifact);
884
+ return context.store.db
885
+ .prepare(`SELECT segment_id FROM runtime_media_segments
886
+ WHERE generation=? AND artifact_id=?
887
+ ORDER BY segment_id`)
888
+ .all(generation, artifact.artifact_id).map((row) => row.segment_id);
889
+ }
890
+ const range = reference.kind === "frame"
891
+ ? undefined
892
+ : normalizeSourceRange(reference.range);
893
+ const point = reference.kind === "frame"
894
+ ? normalizeSourcePoint(reference.source)
895
+ : undefined;
896
+ const rows = context.store.db
897
+ .prepare(`SELECT segment_id, source_range_json
898
+ FROM runtime_media_segments
899
+ WHERE generation=? AND stream_id=? AND object_hash=?
900
+ ORDER BY representative_tick, segment_id`)
901
+ .all(generation, point?.streamId ?? range?.streamId, point?.objectHash ?? range?.objectHash);
902
+ return rows
903
+ .filter((row) => {
904
+ if (!row.source_range_json)
905
+ return false;
906
+ const candidate = parseJson(row.source_range_json, null);
907
+ if (!candidate)
908
+ return false;
909
+ if (point) {
910
+ return (candidate.startTick <= point.tick
911
+ && point.tick < sourceRangeEndTick(candidate));
912
+ }
913
+ return range
914
+ ? rangesOverlap(candidate.startTick, sourceRangeEndTick(candidate), range.startTick, sourceRangeEndTick(range))
915
+ : false;
916
+ })
917
+ .map((row) => row.segment_id);
918
+ }
919
+ function addFingerprintSignals(context, candidates, queryRows, generation) {
920
+ const segmentIds = unique(queryRows.map((row) => row.segment_id));
921
+ if (segmentIds.length === 0)
922
+ return;
923
+ const fingerprints = context.store.db
924
+ .prepare(`SELECT kind, value FROM runtime_segment_fingerprints
925
+ WHERE segment_id IN (${segmentIds.map(() => "?").join(",")})
926
+ ORDER BY kind, value`)
927
+ .all(...segmentIds);
928
+ addPreparedFingerprintSignals(context, candidates, fingerprints, generation);
929
+ }
930
+ function addPreparedFingerprintSignals(context, candidates, fingerprints, generation) {
931
+ for (const fingerprint of fingerprints) {
932
+ const rows = context.store.db
933
+ .prepare(`SELECT segment_id FROM runtime_segment_fingerprints
934
+ WHERE generation=? AND kind=? AND value=?
935
+ ORDER BY segment_id`)
936
+ .all(generation.generation, fingerprint.kind, fingerprint.value);
937
+ rows.forEach((row, index) => {
938
+ const candidate = candidates.get(row.segment_id);
939
+ if (!candidate)
940
+ return;
941
+ candidate.signals.push({
942
+ kind: fingerprint.kind === "sha256" ? "exact" : "near",
943
+ rank: index + 1,
944
+ score: 1,
945
+ explanation: fingerprint.kind === "sha256"
946
+ ? "Exact source fingerprint matched"
947
+ : "Near-duplicate fingerprint matched",
948
+ });
949
+ });
950
+ }
951
+ }
952
+ function finalizeCandidate(candidate) {
953
+ const sortedSignals = [...candidate.signals].sort((left, right) => signalPriority(left.kind) - signalPriority(right.kind)
954
+ || left.rank - right.rank);
955
+ const rrf = sortedSignals.reduce((sum, signal) => sum + 1 / (60 + signal.rank), 0);
956
+ const exactBoost = sortedSignals.some((signal) => signal.kind === "exact")
957
+ ? 1
958
+ : sortedSignals.some((signal) => signal.kind === "near")
959
+ ? 0.25
960
+ : 0;
961
+ return { ...candidate, signals: sortedSignals, score: rrf + exactBoost };
962
+ }
963
+ function compareCandidates(left, right) {
964
+ return (right.score - left.score
965
+ || left.segment.artifact_id.localeCompare(right.segment.artifact_id)
966
+ || locationKey(left.segment).localeCompare(locationKey(right.segment))
967
+ || left.segment.segment_id.localeCompare(right.segment.segment_id));
968
+ }
969
+ function collapseOverlaps(candidates) {
970
+ const accepted = [];
971
+ const intervals = new Map();
972
+ for (const candidate of candidates) {
973
+ const range = segmentRange(candidate.segment);
974
+ if (!range) {
975
+ accepted.push(candidate);
976
+ continue;
977
+ }
978
+ const key = `${candidate.segment.artifact_id}\u0000${range.streamId}`;
979
+ const interval = {
980
+ start: range.startTick,
981
+ end: sourceRangeEndTick(range),
982
+ duration: range.durationTicks,
983
+ segmentId: candidate.segment.segment_id,
984
+ };
985
+ const root = intervals.get(key) ?? null;
986
+ if (hasDuplicateInterval(root, interval))
987
+ continue;
988
+ intervals.set(key, insertInterval(root, interval));
989
+ accepted.push(candidate);
990
+ }
991
+ return accepted;
992
+ }
993
+ function hasDuplicateInterval(node, candidate) {
994
+ if (!node || node.maxEnd <= candidate.start)
995
+ return false;
996
+ if (node.left
997
+ && node.left.maxEnd > candidate.start
998
+ && hasDuplicateInterval(node.left, candidate)) {
999
+ return true;
1000
+ }
1001
+ const other = node.interval;
1002
+ if (other.start < candidate.end) {
1003
+ const overlap = Math.max(0, Math.min(candidate.end, other.end) - Math.max(candidate.start, other.start));
1004
+ if (overlap / Math.min(candidate.duration, other.duration) >= 0.8) {
1005
+ return true;
1006
+ }
1007
+ }
1008
+ else {
1009
+ return false;
1010
+ }
1011
+ return hasDuplicateInterval(node.right, candidate);
1012
+ }
1013
+ function insertInterval(node, interval) {
1014
+ if (!node) {
1015
+ return {
1016
+ interval,
1017
+ priority: intervalPriority(interval),
1018
+ maxEnd: interval.end,
1019
+ left: null,
1020
+ right: null,
1021
+ };
1022
+ }
1023
+ if (compareIntervals(interval, node.interval) < 0) {
1024
+ node.left = insertInterval(node.left, interval);
1025
+ if (node.left.priority > node.priority)
1026
+ node = rotateIntervalRight(node);
1027
+ }
1028
+ else {
1029
+ node.right = insertInterval(node.right, interval);
1030
+ if (node.right.priority > node.priority)
1031
+ node = rotateIntervalLeft(node);
1032
+ }
1033
+ refreshMaxEnd(node);
1034
+ return node;
1035
+ }
1036
+ function rotateIntervalLeft(node) {
1037
+ const pivot = node.right;
1038
+ if (!pivot)
1039
+ return node;
1040
+ node.right = pivot.left;
1041
+ pivot.left = node;
1042
+ refreshMaxEnd(node);
1043
+ refreshMaxEnd(pivot);
1044
+ return pivot;
1045
+ }
1046
+ function rotateIntervalRight(node) {
1047
+ const pivot = node.left;
1048
+ if (!pivot)
1049
+ return node;
1050
+ node.left = pivot.right;
1051
+ pivot.right = node;
1052
+ refreshMaxEnd(node);
1053
+ refreshMaxEnd(pivot);
1054
+ return pivot;
1055
+ }
1056
+ function refreshMaxEnd(node) {
1057
+ node.maxEnd = Math.max(node.interval.end, node.left?.maxEnd ?? Number.NEGATIVE_INFINITY, node.right?.maxEnd ?? Number.NEGATIVE_INFINITY);
1058
+ }
1059
+ function compareIntervals(left, right) {
1060
+ return (left.start - right.start
1061
+ || left.end - right.end
1062
+ || left.segmentId.localeCompare(right.segmentId));
1063
+ }
1064
+ function intervalPriority(interval) {
1065
+ let hash = 2_166_136_261;
1066
+ const key = `${interval.segmentId}:${interval.start}:${interval.end}`;
1067
+ for (let index = 0; index < key.length; index += 1) {
1068
+ hash ^= key.charCodeAt(index);
1069
+ hash = Math.imul(hash, 16_777_619);
1070
+ }
1071
+ return hash >>> 0;
1072
+ }
1073
+ function candidateToHit(candidate) {
1074
+ return {
1075
+ artifactId: candidate.segment.artifact_id,
1076
+ artifactSlug: candidate.segment.artifact_slug,
1077
+ artifactKind: candidate.segment.artifact_kind,
1078
+ location: segmentLocation(candidate.segment),
1079
+ ...(candidate.segment.representative_tick === null
1080
+ ? {}
1081
+ : { representativeTick: candidate.segment.representative_tick }),
1082
+ score: candidate.score,
1083
+ signals: candidate.signals,
1084
+ ...(candidate.excerpt ? { excerpt: candidate.excerpt } : {}),
1085
+ indexManifestIds: [candidate.segment.manifest_id],
1086
+ };
1087
+ }
1088
+ function segmentLocation(segment) {
1089
+ const range = segmentRange(segment);
1090
+ if (range) {
1091
+ return {
1092
+ kind: "timed",
1093
+ artifactId: segment.artifact_id,
1094
+ range,
1095
+ };
1096
+ }
1097
+ return {
1098
+ kind: segment.segment_kind === "document" ? "document" : "still",
1099
+ artifactId: segment.artifact_id,
1100
+ sourcePath: segment.source_path ?? "unknown",
1101
+ objectHash: segment.object_hash,
1102
+ ...(segment.segment_kind === "document"
1103
+ ? { startUtf8Byte: 0, endUtf8Byte: 1 }
1104
+ : {}),
1105
+ };
1106
+ }
1107
+ function segmentRange(segment) {
1108
+ return segment.source_range_json
1109
+ ? parseJson(segment.source_range_json, null)
1110
+ : null;
1111
+ }
1112
+ function locationKey(segment) {
1113
+ return canonicalJson(segmentLocation(segment));
1114
+ }
1115
+ function searchCoverage(context, artifactReference) {
1116
+ const artifactId = artifactReference
1117
+ ? context.artifactRow(artifactReference).artifact_id
1118
+ : undefined;
1119
+ const active = activeGenerations(context);
1120
+ const rows = coverageRows(context, artifactId
1121
+ ? `WHERE artifact_id=? AND generation IN (${active.map(() => "?").join(",")})`
1122
+ : active.length > 0
1123
+ ? `WHERE generation IN (${active.map(() => "?").join(",")})`
1124
+ : "WHERE 1=0", artifactId
1125
+ ? [artifactId, ...active.map((item) => item.generation)]
1126
+ : active.map((item) => item.generation));
1127
+ const allArtifacts = context.store.db
1128
+ .prepare("SELECT COUNT(*) AS count FROM artifacts")
1129
+ .get();
1130
+ const indexedArtifactCount = new Set(rows.map((row) => row.artifactId)).size;
1131
+ const state = aggregateCoverageState(rows);
1132
+ const generation = hashJson(active.map((item) => [item.embedding_space, item.generation]));
1133
+ const modalities = unique(active.flatMap((item) => requiredManifest(context, item.manifest_id).modalities)).map((modality) => {
1134
+ const relevant = rows.filter((row) => phaseForModality(modality) === row.phase);
1135
+ return {
1136
+ modality,
1137
+ state: aggregateCoverageState(relevant),
1138
+ indexedUnits: relevant.reduce((sum, row) => sum + row.indexedUnits, 0),
1139
+ ...(relevant.some((row) => row.totalUnits !== undefined)
1140
+ ? {
1141
+ totalUnits: relevant.reduce((sum, row) => sum + (row.totalUnits ?? 0), 0),
1142
+ }
1143
+ : {}),
1144
+ languageCoverage: modality === "speech" || modality === "ocr"
1145
+ ? "best-effort"
1146
+ : "unsupported",
1147
+ manifestId: active.find((item) => requiredManifest(context, item.manifest_id).modalities.includes(modality))?.manifest_id,
1148
+ };
1149
+ });
1150
+ return {
1151
+ state,
1152
+ generation,
1153
+ modalities,
1154
+ indexedArtifactCount,
1155
+ totalArtifactCount: artifactId ? 1 : allArtifacts.count,
1156
+ partialResults: state !== "ready",
1157
+ };
1158
+ }
1159
+ function coverageRows(context, clause, params) {
1160
+ const rows = context.store.db
1161
+ .prepare(`SELECT artifact_id, object_hash, manifest_id, generation, phase,
1162
+ state, covered_ranges_json, indexed_units, total_units,
1163
+ retryable, error_json, cursor, updated_at
1164
+ FROM runtime_index_coverage ${clause}
1165
+ ORDER BY artifact_id, manifest_id, generation, phase`)
1166
+ .all(...params);
1167
+ return rows.map((row) => ({
1168
+ artifactId: row.artifact_id,
1169
+ objectHash: row.object_hash,
1170
+ manifestId: row.manifest_id,
1171
+ phase: row.phase,
1172
+ state: row.state,
1173
+ coveredRanges: parseJson(row.covered_ranges_json, []),
1174
+ indexedUnits: row.indexed_units,
1175
+ ...(row.total_units === null ? {} : { totalUnits: row.total_units }),
1176
+ ...(row.cursor === null ? {} : { nextCursor: row.cursor }),
1177
+ retryable: row.retryable === 1,
1178
+ ...(row.error_json
1179
+ ? { error: parseJson(row.error_json, {
1180
+ code: "INTERNAL_ERROR",
1181
+ message: "Unknown index error",
1182
+ }) }
1183
+ : {}),
1184
+ updatedAt: row.updated_at,
1185
+ }));
1186
+ }
1187
+ function invalidateCoverage(context, artifactReference, objectHash) {
1188
+ const artifact = context.artifactRow(artifactReference);
1189
+ return context.store.runtime((now) => {
1190
+ const result = objectHash
1191
+ ? context.store.db
1192
+ .prepare(`UPDATE runtime_index_coverage
1193
+ SET state='stale', updated_at=?
1194
+ WHERE artifact_id=? AND object_hash=?`)
1195
+ .run(now, artifact.artifact_id, objectHash)
1196
+ : context.store.db
1197
+ .prepare(`UPDATE runtime_index_coverage
1198
+ SET state='stale', updated_at=?
1199
+ WHERE artifact_id=?`)
1200
+ .run(now, artifact.artifact_id);
1201
+ return Number(result.changes);
1202
+ });
1203
+ }
1204
+ function cleanupRetired(context) {
1205
+ return context.store.runtime(() => {
1206
+ const retired = context.store.db
1207
+ .prepare(`SELECT generation FROM runtime_index_generations
1208
+ WHERE state='retired'`)
1209
+ .all().map((row) => row.generation);
1210
+ if (retired.length === 0)
1211
+ return { removedSegments: 0 };
1212
+ const placeholders = retired.map(() => "?").join(",");
1213
+ const result = context.store.db
1214
+ .prepare(`DELETE FROM runtime_media_segments
1215
+ WHERE generation IN (${placeholders})`)
1216
+ .run(...retired);
1217
+ context.store.db
1218
+ .prepare(`DELETE FROM runtime_index_coverage
1219
+ WHERE generation IN (${placeholders})`)
1220
+ .run(...retired);
1221
+ context.store.db
1222
+ .prepare(`DELETE FROM runtime_index_batches
1223
+ WHERE generation IN (${placeholders})`)
1224
+ .run(...retired);
1225
+ context.store.db
1226
+ .prepare(`DELETE FROM runtime_index_generations
1227
+ WHERE generation IN (${placeholders})`)
1228
+ .run(...retired);
1229
+ return { removedSegments: Number(result.changes) };
1230
+ });
1231
+ }
1232
+ function temporalStats(context) {
1233
+ const count = (table, clause = "") => context.store.db
1234
+ .prepare(`SELECT COUNT(*) AS count FROM ${table} ${clause}`)
1235
+ .get().count;
1236
+ return {
1237
+ activeGenerations: count("runtime_index_generations", "WHERE state='active'"),
1238
+ segments: count("runtime_media_segments"),
1239
+ textObservations: count("runtime_segment_text"),
1240
+ embeddings: count("runtime_segment_embeddings"),
1241
+ fingerprints: count("runtime_segment_fingerprints"),
1242
+ };
1243
+ }
1244
+ function assertArtifactObject(context, artifactId, objectHash) {
1245
+ const object = context.store.db
1246
+ .prepare(`SELECT 1 AS present FROM artifact_files
1247
+ WHERE artifact_id=? AND object_hash=? LIMIT 1`)
1248
+ .get(artifactId, objectHash);
1249
+ if (!object) {
1250
+ throw new EngineFault({
1251
+ code: "OBJECT_UNAVAILABLE",
1252
+ message: "Artifact does not currently reference the requested object",
1253
+ details: { artifactId, objectHash },
1254
+ });
1255
+ }
1256
+ }
1257
+ function validateQuery(query, prepared) {
1258
+ if (!query.text && !query.reference && !prepared) {
1259
+ throw new EngineFault({
1260
+ code: "INVALID_INPUT",
1261
+ message: "Search requires text, reference media, or both",
1262
+ });
1263
+ }
1264
+ if (query.reference && prepared) {
1265
+ throw new EngineFault({
1266
+ code: "INVALID_INPUT",
1267
+ message: "Search cannot combine artifact and prepared references",
1268
+ });
1269
+ }
1270
+ if (query.text !== undefined)
1271
+ requiredText(query.text, "Search text");
1272
+ if (query.limit !== undefined)
1273
+ safeIntegerAtLeast(query.limit, 1, "Search limit");
1274
+ if (query.minScore !== undefined && !Number.isFinite(query.minScore)) {
1275
+ throw new Error("Search minScore must be finite");
1276
+ }
1277
+ }
1278
+ function validatePreparedReference(reference, options) {
1279
+ requiredText(reference.embeddingSpace, "Prepared reference embedding space");
1280
+ if (reference.kind === "image") {
1281
+ preparedVector(reference.vector);
1282
+ if (options.range) {
1283
+ throw new EngineFault({
1284
+ code: "INVALID_INPUT",
1285
+ message: "Image references do not accept a time range",
1286
+ });
1287
+ }
1288
+ for (const fingerprint of reference.fingerprints ?? []) {
1289
+ requiredText(fingerprint.kind, "Prepared fingerprint kind");
1290
+ requiredText(fingerprint.value, "Prepared fingerprint value");
1291
+ }
1292
+ return;
1293
+ }
1294
+ if (reference.samples.length === 0 || reference.samples.length > 16) {
1295
+ throw new EngineFault({
1296
+ code: "INVALID_INPUT",
1297
+ message: "Prepared video references require between 1 and 16 samples",
1298
+ });
1299
+ }
1300
+ let previousOffset = -1;
1301
+ for (const sample of reference.samples) {
1302
+ if (!Number.isFinite(sample.offsetMs) || sample.offsetMs < 0) {
1303
+ throw new EngineFault({
1304
+ code: "INVALID_INPUT",
1305
+ message: "Prepared video sample offsets must be finite and non-negative",
1306
+ });
1307
+ }
1308
+ if (sample.offsetMs <= previousOffset) {
1309
+ throw new EngineFault({
1310
+ code: "INVALID_INPUT",
1311
+ message: "Prepared video sample offsets must be strictly increasing",
1312
+ });
1313
+ }
1314
+ previousOffset = sample.offsetMs;
1315
+ preparedVector(sample.vector);
1316
+ }
1317
+ const range = options.range;
1318
+ if (range) {
1319
+ if (!Number.isFinite(range.startMs) || range.startMs < 0) {
1320
+ throw new EngineFault({
1321
+ code: "INVALID_RANGE",
1322
+ message: "Prepared video range start must be finite and non-negative",
1323
+ });
1324
+ }
1325
+ if (range.durationMs !== undefined
1326
+ && (!Number.isFinite(range.durationMs) || range.durationMs <= 0)) {
1327
+ throw new EngineFault({
1328
+ code: "INVALID_RANGE",
1329
+ message: "Prepared video range duration must be finite and positive",
1330
+ });
1331
+ }
1332
+ }
1333
+ preparedVideoSamples(reference, options);
1334
+ }
1335
+ function preparedVideoSamples(reference, options) {
1336
+ const range = options.range;
1337
+ const endMs = range?.durationMs === undefined
1338
+ ? Number.POSITIVE_INFINITY
1339
+ : range.startMs + range.durationMs;
1340
+ const samples = range
1341
+ ? reference.samples.filter((sample) => sample.offsetMs >= range.startMs && sample.offsetMs <= endMs)
1342
+ : reference.samples;
1343
+ if (samples.length === 0) {
1344
+ throw new EngineFault({
1345
+ code: "INVALID_RANGE",
1346
+ message: "Prepared video range does not contain a sampled frame",
1347
+ });
1348
+ }
1349
+ return samples;
1350
+ }
1351
+ function preparedVector(vector, dimensions) {
1352
+ if (!Array.isArray(vector)
1353
+ || vector.length === 0
1354
+ || vector.some((value) => !Number.isFinite(value))) {
1355
+ throw new EngineFault({
1356
+ code: "INVALID_INPUT",
1357
+ message: "Prepared reference vectors must contain finite values",
1358
+ });
1359
+ }
1360
+ if (dimensions !== undefined && vector.length !== dimensions) {
1361
+ throw new EngineFault({
1362
+ code: "MANIFEST_INCOMPATIBLE",
1363
+ message: "Prepared reference vector dimensions do not match the index",
1364
+ });
1365
+ }
1366
+ return Float32Array.from(vector);
1367
+ }
1368
+ function aggregateCoverageState(rows) {
1369
+ if (rows.length === 0)
1370
+ return "not-indexed";
1371
+ if (rows.some((row) => row.state === "failed"))
1372
+ return "failed";
1373
+ if (rows.some((row) => row.state === "stale"))
1374
+ return "stale";
1375
+ if (rows.every((row) => row.state === "ready"))
1376
+ return "ready";
1377
+ return "partial";
1378
+ }
1379
+ function phaseForModality(modality) {
1380
+ if (modality === "visual")
1381
+ return "visual";
1382
+ if (modality === "audio")
1383
+ return "audio";
1384
+ if (modality === "ocr")
1385
+ return "ocr";
1386
+ if (modality === "speech")
1387
+ return "transcript";
1388
+ return "lexical";
1389
+ }
1390
+ function signalPriority(kind) {
1391
+ return ["exact", "near", "speech", "ocr", "visual", "audio", "metadata"].indexOf(kind);
1392
+ }
1393
+ function vectorToBlob(vector) {
1394
+ const values = Float32Array.from(vector);
1395
+ return Buffer.from(values.buffer, values.byteOffset, values.byteLength);
1396
+ }
1397
+ function blobToVector(blob, dimensions) {
1398
+ const bytes = Buffer.from(blob);
1399
+ if (bytes.byteLength !== dimensions * Float32Array.BYTES_PER_ELEMENT) {
1400
+ throw new EngineFault({
1401
+ code: "MANIFEST_INCOMPATIBLE",
1402
+ message: "Stored vector dimensions do not match its byte length",
1403
+ });
1404
+ }
1405
+ const copy = new Uint8Array(bytes);
1406
+ return new Float32Array(copy.buffer);
1407
+ }
1408
+ function cosineSimilarity(left, right) {
1409
+ if (left.length !== right.length || left.length === 0) {
1410
+ throw new EngineFault({
1411
+ code: "MANIFEST_INCOMPATIBLE",
1412
+ message: "Cannot compare incompatible vector dimensions",
1413
+ });
1414
+ }
1415
+ let dot = 0;
1416
+ let leftNorm = 0;
1417
+ let rightNorm = 0;
1418
+ for (let index = 0; index < left.length; index += 1) {
1419
+ const leftValue = left[index];
1420
+ const rightValue = right[index];
1421
+ dot += leftValue * rightValue;
1422
+ leftNorm += leftValue * leftValue;
1423
+ rightNorm += rightValue * rightValue;
1424
+ }
1425
+ if (leftNorm === 0 || rightNorm === 0)
1426
+ return 0;
1427
+ return dot / (Math.sqrt(leftNorm) * Math.sqrt(rightNorm));
1428
+ }
1429
+ function averageVectors(vectors) {
1430
+ const first = vectors[0];
1431
+ if (!first)
1432
+ return new Float32Array();
1433
+ const result = new Float32Array(first.length);
1434
+ for (const vector of vectors) {
1435
+ if (vector.length !== result.length) {
1436
+ throw new EngineFault({
1437
+ code: "MANIFEST_INCOMPATIBLE",
1438
+ message: "Reference vectors have incompatible dimensions",
1439
+ });
1440
+ }
1441
+ vector.forEach((value, index) => {
1442
+ result[index] = result[index] + value;
1443
+ });
1444
+ }
1445
+ for (let index = 0; index < result.length; index += 1) {
1446
+ result[index] = result[index] / vectors.length;
1447
+ }
1448
+ return result;
1449
+ }
1450
+ function encodeCursor(generation, offset) {
1451
+ return Buffer.from(canonicalJson({ generation, offset }), "utf8").toString("base64url");
1452
+ }
1453
+ function decodeCursor(cursor, generation) {
1454
+ if (!cursor)
1455
+ return 0;
1456
+ const parsed = parseJson(Buffer.from(cursor, "base64url").toString("utf8"), {});
1457
+ if (parsed.generation !== generation
1458
+ || !Number.isSafeInteger(parsed.offset)
1459
+ || (parsed.offset ?? -1) < 0) {
1460
+ throw new EngineFault({
1461
+ code: "STALE_REVISION",
1462
+ message: "Search cursor belongs to another index generation",
1463
+ });
1464
+ }
1465
+ return parsed.offset;
1466
+ }
1467
+ function hashJson(value) {
1468
+ return `sha256:${createHash("sha256")
1469
+ .update(canonicalJson(value))
1470
+ .digest("hex")}`;
1471
+ }
1472
+ function deterministicUuid(namespace, value) {
1473
+ const bytes = createHash("sha256")
1474
+ .update(namespace)
1475
+ .update("\0")
1476
+ .update(value)
1477
+ .digest()
1478
+ .subarray(0, 16);
1479
+ bytes[6] = (bytes[6] & 0x0f) | 0x70;
1480
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
1481
+ const hex = bytes.toString("hex");
1482
+ return [
1483
+ hex.slice(0, 8),
1484
+ hex.slice(8, 12),
1485
+ hex.slice(12, 16),
1486
+ hex.slice(16, 20),
1487
+ hex.slice(20),
1488
+ ].join("-");
1489
+ }
1490
+ function requiredText(value, label) {
1491
+ const normalized = value.trim();
1492
+ if (!normalized)
1493
+ throw new Error(`${label} is required`);
1494
+ return normalized;
1495
+ }
1496
+ function safeIntegerAtLeast(value, minimum, label) {
1497
+ if (!Number.isSafeInteger(value) || value < minimum) {
1498
+ throw new Error(`${label} must be a safe integer of at least ${minimum}`);
1499
+ }
1500
+ }
1501
+ function rangesOverlap(leftStart, leftEnd, rightStart, rightEnd) {
1502
+ return leftStart < rightEnd && rightStart < leftEnd;
1503
+ }
1504
+ function unique(values) {
1505
+ return [...new Set(values)];
1506
+ }
1507
+ //# sourceMappingURL=temporal-search.js.map