@absolutejs/absolute 0.19.0-beta.615 → 0.19.0-beta.616

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 (37) hide show
  1. package/dist/ai/client/index.js +60 -43
  2. package/dist/ai/client/index.js.map +7 -7
  3. package/dist/ai/client/ui.js +5 -1
  4. package/dist/ai/client/ui.js.map +3 -3
  5. package/dist/ai/index.js +1101 -196
  6. package/dist/ai/index.js.map +14 -12
  7. package/dist/ai/rag/quality.js +16 -3
  8. package/dist/ai/rag/quality.js.map +4 -4
  9. package/dist/ai/rag/ui.js +5 -1
  10. package/dist/ai/rag/ui.js.map +3 -3
  11. package/dist/ai-client/angular/ai/index.js +44 -40
  12. package/dist/ai-client/react/ai/index.js +46 -42
  13. package/dist/ai-client/vue/ai/index.js +44 -40
  14. package/dist/angular/ai/index.js +46 -42
  15. package/dist/angular/ai/index.js.map +6 -6
  16. package/dist/react/ai/index.js +64 -47
  17. package/dist/react/ai/index.js.map +9 -9
  18. package/dist/src/ai/client/actions.d.ts +1 -1
  19. package/dist/src/ai/index.d.ts +2 -2
  20. package/dist/src/ai/protocol.d.ts +1 -1
  21. package/dist/src/ai/rag/accessControl.d.ts +2 -0
  22. package/dist/src/ai/rag/chat.d.ts +21 -23
  23. package/dist/src/ai/rag/index.d.ts +4 -2
  24. package/dist/src/ai/rag/ingestion.d.ts +12 -3
  25. package/dist/src/ai/rag/jobState.d.ts +2 -0
  26. package/dist/src/vue/ai/useRAG.d.ts +26 -0
  27. package/dist/src/vue/ai/useRAGChunkPreview.d.ts +4 -0
  28. package/dist/src/vue/ai/useRAGDocuments.d.ts +4 -0
  29. package/dist/src/vue/ai/useRAGEvaluate.d.ts +14 -0
  30. package/dist/src/vue/ai/useRAGIndexAdmin.d.ts +2 -0
  31. package/dist/src/vue/ai/useRAGSearch.d.ts +2 -0
  32. package/dist/svelte/ai/index.js +60 -43
  33. package/dist/svelte/ai/index.js.map +7 -7
  34. package/dist/types/ai.d.ts +147 -0
  35. package/dist/vue/ai/index.js +60 -43
  36. package/dist/vue/ai/index.js.map +7 -7
  37. package/package.json +7 -7
package/dist/ai/index.js CHANGED
@@ -187,7 +187,7 @@ var parseAIMessage = (raw) => {
187
187
  return null;
188
188
  }
189
189
  };
190
- var serializeAIMessage = (msg) => JSON.stringify(msg);
190
+ var serializeAIMessage = (message) => JSON.stringify(message);
191
191
 
192
192
  // src/ai/rag/grounding.ts
193
193
  var getContextString = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
@@ -1275,7 +1275,10 @@ var evaluateRAGCollectionCases = async ({
1275
1275
  const expectedIds = normalizeExpectedIds(mode === "chunkId" ? caseInput.expectedChunkIds ?? [] : mode === "source" ? caseInput.expectedSources ?? [] : caseInput.expectedDocumentIds ?? []);
1276
1276
  const topK = typeof caseInput.topK === "number" ? caseInput.topK : typeof input.topK === "number" ? input.topK : defaultTopK;
1277
1277
  const searchInput = {
1278
- filter: typeof caseInput.filter === "object" ? caseInput.filter : input.filter,
1278
+ filter: caseInput.corpusKey ? {
1279
+ ...(typeof caseInput.filter === "object" ? caseInput.filter : input.filter) ?? {},
1280
+ corpusKey: caseInput.corpusKey
1281
+ } : typeof caseInput.filter === "object" ? caseInput.filter : input.filter,
1279
1282
  model: caseInput.model ?? input.model,
1280
1283
  query,
1281
1284
  rerank,
@@ -1677,6 +1680,7 @@ var buildEvaluationCaseTraceSnapshot = ({
1677
1680
  return {
1678
1681
  candidateTopK: currentTrace?.candidateTopK ?? 0,
1679
1682
  caseId: caseResult.caseId,
1683
+ corpusKey: caseResult.corpusKey,
1680
1684
  inputFilter: filter,
1681
1685
  finalCount: currentTrace?.resultCounts.final ?? 0,
1682
1686
  label: caseResult.label,
@@ -3229,11 +3233,15 @@ var persistRAGRetrievalReleaseLaneEscalationPolicyHistory = async ({
3229
3233
  };
3230
3234
  var buildRAGEvaluationResponse = (cases) => {
3231
3235
  const totalCases = cases.length;
3236
+ const corpusKeys = [
3237
+ ...new Set(cases.flatMap((entry) => entry.corpusKey ?? []))
3238
+ ];
3232
3239
  const passedCases = cases.filter((entry) => entry.status === "pass").length;
3233
3240
  const partialCases = cases.filter((entry) => entry.status === "partial").length;
3234
3241
  const failedCases = cases.filter((entry) => entry.status === "fail").length;
3235
3242
  return {
3236
3243
  cases,
3244
+ ...corpusKeys.length > 0 ? { corpusKeys } : {},
3237
3245
  elapsedMs: cases.reduce((sum, result) => sum + result.elapsedMs, 0),
3238
3246
  ok: true,
3239
3247
  passingRate: totalCases > 0 ? passedCases / totalCases * 100 : 0,
@@ -3459,6 +3467,9 @@ var compareRAGRetrievalStrategies = async ({
3459
3467
  traceSummary: entry.traceSummary
3460
3468
  })));
3461
3469
  return {
3470
+ corpusKeys: [
3471
+ ...new Set(entries.flatMap((entry) => entry.response.corpusKeys ?? []))
3472
+ ],
3462
3473
  entries,
3463
3474
  leaderboard,
3464
3475
  summary: summarizeRAGRetrievalComparison(entries),
@@ -3491,6 +3502,7 @@ var compareRAGRetrievalTraceSummaries = (current, previous) => ({
3491
3502
  });
3492
3503
  var buildSearchTraceResultSnapshots = (results) => results.map((result) => ({
3493
3504
  chunkId: result.chunkId,
3505
+ corpusKey: result.corpusKey ?? (typeof result.metadata?.corpusKey === "string" ? result.metadata.corpusKey : undefined),
3494
3506
  documentId: typeof result.metadata?.documentId === "string" ? result.metadata.documentId : undefined,
3495
3507
  score: result.score,
3496
3508
  source: result.source,
@@ -3690,6 +3702,7 @@ var summarizeRAGEvaluationCase = ({
3690
3702
  const status = expectedCount === 0 ? "partial" : matchedCount === expectedCount ? "pass" : matchedCount > 0 ? "partial" : "fail";
3691
3703
  return {
3692
3704
  caseId: caseInput.id ?? `case-${caseIndex + 1}`,
3705
+ corpusKey: caseInput.corpusKey,
3693
3706
  elapsedMs,
3694
3707
  expectedCount,
3695
3708
  expectedIds,
@@ -4289,10 +4302,14 @@ var buildProvenanceLabel2 = (metadata) => {
4289
4302
  const transcriptSource = getContextString2(metadata.transcriptSource);
4290
4303
  const pdfTextMode = getContextString2(metadata.pdfTextMode);
4291
4304
  const ocrEngine = getContextString2(metadata.ocrEngine);
4305
+ const extractorRegistryMatch = getContextString2(metadata.extractorRegistryMatch);
4306
+ const chunkingProfile = getContextString2(metadata.chunkingProfile);
4292
4307
  const ocrConfidence = getContextNumber2(metadata.ocrRegionConfidence) ?? getContextNumber2(metadata.ocrConfidence);
4293
4308
  const labels = [
4294
4309
  pdfTextMode ? `PDF ${pdfTextMode}` : "",
4295
4310
  ocrEngine ? `OCR ${ocrEngine}` : "",
4311
+ extractorRegistryMatch ? `Extractor ${extractorRegistryMatch}` : "",
4312
+ chunkingProfile ? `Chunking ${chunkingProfile}` : "",
4296
4313
  typeof ocrConfidence === "number" ? `Confidence ${ocrConfidence.toFixed(2)}` : "",
4297
4314
  mediaKind ? `Media ${mediaKind}` : "",
4298
4315
  transcriptSource ? `Transcript ${transcriptSource}` : "",
@@ -6741,12 +6758,12 @@ var checkBackpressure = async (socket) => {
6741
6758
  await delay(BACKPRESSURE_DELAY);
6742
6759
  }
6743
6760
  };
6744
- var sendMessage = async (socket, msg) => {
6761
+ var sendMessage = async (socket, message) => {
6745
6762
  if (socket.readyState !== WS_OPEN) {
6746
6763
  return false;
6747
6764
  }
6748
6765
  await checkBackpressure(socket);
6749
- socket.send(serializeAIMessage(msg));
6766
+ socket.send(serializeAIMessage(message));
6750
6767
  return true;
6751
6768
  };
6752
6769
  var buildToolDefinitions = (tools) => Object.entries(tools).map(([name, def]) => ({
@@ -9689,6 +9706,7 @@ var createEmailExtractor = () => ({
9689
9706
  chunking: input.chunking,
9690
9707
  contentType: attachment.contentType,
9691
9708
  data: attachment.data,
9709
+ extractorRegistry: input.extractorRegistry,
9692
9710
  format: inferFormatFromContentType(attachment.contentType ?? null) ?? inferFormatFromName(attachment.fileName),
9693
9711
  metadata: {
9694
9712
  ...messageMetadata,
@@ -9844,6 +9862,8 @@ ${slide.text}`),
9844
9862
  });
9845
9863
  var createRAGArchiveExpander = (expander) => expander;
9846
9864
  var createRAGFileExtractor = (extractor) => extractor;
9865
+ var createRAGFileExtractorRegistry = (registry) => registry;
9866
+ var createRAGChunkingRegistry = (registry) => registry;
9847
9867
  var createRAGImageOCRExtractor = (provider) => ({
9848
9868
  name: `absolute_image_ocr:${provider.name}`,
9849
9869
  supports: imageExtractorSupports,
@@ -9934,11 +9954,12 @@ var createTextFileExtractor = () => ({
9934
9954
  title: input.title
9935
9955
  })
9936
9956
  });
9937
- var expandArchiveEntry = async (entry, archiveInput, extractors) => {
9957
+ var expandArchiveEntry = async (entry, archiveInput, extractors, registry) => {
9938
9958
  const documents = await extractRAGFileDocuments({
9939
9959
  chunking: archiveInput.chunking,
9940
9960
  contentType: entry.contentType,
9941
9961
  data: entry.data,
9962
+ extractorRegistry: archiveInput.extractorRegistry,
9942
9963
  format: entry.format,
9943
9964
  metadata: {
9944
9965
  ...archiveInput.metadata ?? {},
@@ -9952,7 +9973,7 @@ var expandArchiveEntry = async (entry, archiveInput, extractors) => {
9952
9973
  name: basename(entry.path),
9953
9974
  source: archiveInput.source && !archiveInput.source.startsWith("http") ? `${archiveInput.source}#${entry.path}` : entry.path,
9954
9975
  title: basename(entry.path)
9955
- }, extractors);
9976
+ }, extractors, registry);
9956
9977
  return documents;
9957
9978
  };
9958
9979
  var createPDFFileExtractor = () => ({
@@ -10053,8 +10074,83 @@ var DEFAULT_FILE_EXTRACTORS = [
10053
10074
  createPDFFileExtractor(),
10054
10075
  createTextFileExtractor()
10055
10076
  ];
10056
- var resolveFileExtractors = (extractors) => extractors && extractors.length > 0 ? [...extractors, ...DEFAULT_FILE_EXTRACTORS] : DEFAULT_FILE_EXTRACTORS;
10057
- var applyExtractorDefaults = (document, input, extractorName) => ({
10077
+ var resolveExtractorRegistry = (registry) => {
10078
+ if (!registry) {
10079
+ return {
10080
+ defaultOrder: "registry_first",
10081
+ includeDefaults: true,
10082
+ registrations: []
10083
+ };
10084
+ }
10085
+ if (Array.isArray(registry)) {
10086
+ return {
10087
+ defaultOrder: "registry_first",
10088
+ includeDefaults: true,
10089
+ registrations: registry
10090
+ };
10091
+ }
10092
+ return {
10093
+ defaultOrder: registry.defaultOrder ?? "registry_first",
10094
+ includeDefaults: registry.includeDefaults ?? true,
10095
+ registrations: registry.registrations
10096
+ };
10097
+ };
10098
+ var createExtractorRegistryInput = (input) => ({
10099
+ ...input,
10100
+ inferredContentType: input.contentType ?? null,
10101
+ inferredExtension: inferExtensionFromInput(input) || null,
10102
+ inferredFormat: input.format ?? inferFormatFromContentType(input.contentType ?? null) ?? inferFormatFromName(input.path ?? input.source ?? input.name ?? input.title)
10103
+ });
10104
+ var registrationMatches = async (registration, input) => {
10105
+ const normalizedContentType = input.inferredContentType?.toLowerCase();
10106
+ const normalizedExtension = input.inferredExtension?.toLowerCase();
10107
+ const normalizedName = (input.path ?? input.source ?? input.name ?? input.title ?? "").toLowerCase();
10108
+ if (registration.contentTypes?.length && !registration.contentTypes.some((entry) => normalizedContentType === entry.toLowerCase())) {
10109
+ return false;
10110
+ }
10111
+ if (registration.extensions?.length && !registration.extensions.some((entry) => normalizedExtension === entry.toLowerCase())) {
10112
+ return false;
10113
+ }
10114
+ if (registration.formats?.length && (!input.inferredFormat || !registration.formats.includes(input.inferredFormat))) {
10115
+ return false;
10116
+ }
10117
+ if (registration.names?.length && !registration.names.some((entry) => normalizedName.includes(entry.toLowerCase()))) {
10118
+ return false;
10119
+ }
10120
+ if (registration.match) {
10121
+ return registration.match(input);
10122
+ }
10123
+ return true;
10124
+ };
10125
+ var dedupeResolvedExtractors = (extractors) => {
10126
+ const seen = new Set;
10127
+ const ordered = [];
10128
+ for (const entry of extractors) {
10129
+ if (seen.has(entry.extractor.name)) {
10130
+ continue;
10131
+ }
10132
+ seen.add(entry.extractor.name);
10133
+ ordered.push(entry);
10134
+ }
10135
+ return ordered;
10136
+ };
10137
+ var resolveFileExtractors = async (input, extractors, registry) => {
10138
+ const explicit = extractors ?? [];
10139
+ const resolvedRegistry = resolveExtractorRegistry(registry);
10140
+ const registryInput = createExtractorRegistryInput(input);
10141
+ const matchedRegistrations = (await Promise.all(resolvedRegistry.registrations.map(async (registration) => ({
10142
+ matches: await registrationMatches(registration, registryInput),
10143
+ priority: registration.priority ?? 0,
10144
+ registration
10145
+ })))).filter((entry) => entry.matches).sort((left, right) => right.priority - left.priority).map((entry) => ({
10146
+ extractor: entry.registration.extractor,
10147
+ registryMatchName: entry.registration.name ?? entry.registration.extractor.name
10148
+ }));
10149
+ const defaults = resolvedRegistry.includeDefaults ? DEFAULT_FILE_EXTRACTORS.map((extractor) => ({ extractor })) : [];
10150
+ const explicitResolved = explicit.map((extractor) => ({ extractor }));
10151
+ return dedupeResolvedExtractors(resolvedRegistry.defaultOrder === "defaults_first" ? [...explicitResolved, ...defaults, ...matchedRegistrations] : [...explicitResolved, ...matchedRegistrations, ...defaults]);
10152
+ };
10153
+ var applyExtractorDefaults = (document, input, extractorName, registryMatchName) => ({
10058
10154
  chunking: document.chunking ?? input.chunking,
10059
10155
  format: document.format ?? input.format ?? inferFormatFromContentType(document.contentType ?? input.contentType ?? null) ?? inferFormatFromName(document.source ?? input.source ?? input.path ?? input.name),
10060
10156
  id: document.id,
@@ -10062,20 +10158,22 @@ var applyExtractorDefaults = (document, input, extractorName) => ({
10062
10158
  ...input.metadata ?? {},
10063
10159
  ...document.metadata ?? {},
10064
10160
  contentType: document.contentType ?? input.contentType,
10065
- extractor: document.extractor ?? extractorName
10161
+ extractor: document.extractor ?? extractorName,
10162
+ ...registryMatchName ? { extractorRegistryMatch: registryMatchName } : {}
10066
10163
  },
10067
10164
  source: document.source ?? input.source ?? input.path ?? input.name,
10068
10165
  text: document.text,
10069
10166
  title: document.title ?? input.title
10070
10167
  });
10071
- var extractRAGFileDocuments = async (input, extractors) => {
10072
- for (const extractor of resolveFileExtractors(extractors)) {
10168
+ var extractRAGFileDocuments = async (input, extractors, registry) => {
10169
+ for (const resolvedExtractor of await resolveFileExtractors(input, extractors, registry)) {
10170
+ const { extractor, registryMatchName } = resolvedExtractor;
10073
10171
  if (!await extractor.supports(input)) {
10074
10172
  continue;
10075
10173
  }
10076
10174
  const extracted = await extractor.extract(input);
10077
10175
  const documents = Array.isArray(extracted) ? extracted : [extracted];
10078
- return documents.map((document) => applyExtractorDefaults(document, input, extractor.name));
10176
+ return documents.map((document) => applyExtractorDefaults(document, input, extractor.name, registryMatchName));
10079
10177
  }
10080
10178
  throw new Error(`No RAG file extractor matched ${inferNameFromInput(input)}. Register a custom extractor for this file type.`);
10081
10179
  };
@@ -10086,7 +10184,7 @@ var getFirstExtractedDocument = (documents, label) => {
10086
10184
  }
10087
10185
  return document;
10088
10186
  };
10089
- var loadExtractedDocuments = async (input, extractors) => extractRAGFileDocuments(input, extractors);
10187
+ var loadExtractedDocuments = async (input, extractors, registry) => extractRAGFileDocuments(input, extractors, registry);
10090
10188
  var sentenceUnits = (text) => {
10091
10189
  const matches = text.match(/[^.!?\n]+(?:[.!?]+|$)/g);
10092
10190
  if (!matches) {
@@ -10283,11 +10381,92 @@ var resolveChunkingUnits = (text, options) => {
10283
10381
  }
10284
10382
  return paragraphUnits(text);
10285
10383
  };
10286
- var resolveChunkingOptions = (document, defaults) => {
10287
- const maxChunkLength = document.chunking?.maxChunkLength ?? defaults?.maxChunkLength ?? DEFAULT_MAX_CHUNK_LENGTH;
10288
- const chunkOverlap = document.chunking?.chunkOverlap ?? defaults?.chunkOverlap ?? DEFAULT_CHUNK_OVERLAP;
10289
- const minChunkLength = document.chunking?.minChunkLength ?? defaults?.minChunkLength ?? DEFAULT_MIN_CHUNK_LENGTH;
10290
- const strategy = document.chunking?.strategy ?? defaults?.strategy ?? DEFAULT_STRATEGY;
10384
+ var resolveChunkingProfiles = (registry) => {
10385
+ if (!registry) {
10386
+ return [];
10387
+ }
10388
+ const profiles = Array.isArray(registry) ? registry : registry.profiles;
10389
+ return profiles.map((profile, index) => normalizeChunkingProfile(profile, index)).sort((left, right) => right.priority - left.priority).map(({ profile }) => profile);
10390
+ };
10391
+ var normalizeChunkingProfile = (profile, index) => {
10392
+ if ("resolve" in profile) {
10393
+ return {
10394
+ priority: 0,
10395
+ profile
10396
+ };
10397
+ }
10398
+ const options = normalizeChunkingProfileOptions(profile.profile);
10399
+ const sourceSet = profile.sources?.filter((value) => typeof value === "string") ?? [];
10400
+ const documentIdSet = profile.documentIds?.filter((value) => typeof value === "string") ?? [];
10401
+ const formatSet = profile.formats?.filter((value) => typeof value === "string") ?? [];
10402
+ const sourceNativeKindSet = profile.sourceNativeKinds?.filter((value) => typeof value === "string") ?? [];
10403
+ return {
10404
+ priority: profile.priority ?? 0,
10405
+ profile: {
10406
+ name: profile.name ?? `chunking_profile_${String(index + 1).padStart(2, "0")}`,
10407
+ resolve: (input) => {
10408
+ const documentId = input.document.id?.trim() || (typeof input.metadata.documentId === "string" ? input.metadata.documentId : undefined);
10409
+ const source = input.document.source?.trim();
10410
+ if (formatSet.length > 0 && !formatSet.includes(input.format)) {
10411
+ return;
10412
+ }
10413
+ if (sourceNativeKindSet.length > 0 && (!input.sourceNativeKind || !sourceNativeKindSet.includes(input.sourceNativeKind))) {
10414
+ return;
10415
+ }
10416
+ if (sourceSet.length > 0 && (!source || !sourceSet.includes(source))) {
10417
+ return;
10418
+ }
10419
+ if (documentIdSet.length > 0 && (!documentId || !documentIdSet.includes(documentId))) {
10420
+ return;
10421
+ }
10422
+ return options;
10423
+ }
10424
+ }
10425
+ };
10426
+ };
10427
+ var normalizeChunkingProfileOptions = (profile) => isChunkingProfileOptionsWrapper(profile) ? profile.options ?? {} : profile;
10428
+ var isChunkingProfileOptionsWrapper = (profile) => Object.prototype.hasOwnProperty.call(profile, "options");
10429
+ var resolveChunkingProfileOverrides = ({
10430
+ defaults,
10431
+ document,
10432
+ format,
10433
+ normalizedText,
10434
+ registry
10435
+ }) => {
10436
+ const metadata = document.metadata ?? {};
10437
+ const sourceNativeKind = typeof metadata.sourceNativeKind === "string" ? metadata.sourceNativeKind : undefined;
10438
+ for (const profile of resolveChunkingProfiles(registry)) {
10439
+ const options = profile.resolve({
10440
+ defaults,
10441
+ document,
10442
+ format,
10443
+ metadata,
10444
+ normalizedText,
10445
+ sourceNativeKind
10446
+ });
10447
+ if (options) {
10448
+ return {
10449
+ name: profile.name,
10450
+ options
10451
+ };
10452
+ }
10453
+ }
10454
+ return;
10455
+ };
10456
+ var resolveChunkingOptions = (document, defaults, registry, format, normalizedText) => {
10457
+ const resolvedFormat = format ?? inferFormat(document);
10458
+ const resolvedNormalizedText = normalizedText ?? normalizeDocumentText(document.text, resolvedFormat);
10459
+ const profileOverrides = resolveChunkingProfileOverrides({
10460
+ defaults,
10461
+ document,
10462
+ format: resolvedFormat,
10463
+ normalizedText: resolvedNormalizedText,
10464
+ registry
10465
+ });
10466
+ const maxChunkLength = document.chunking?.maxChunkLength ?? profileOverrides?.options.maxChunkLength ?? defaults?.maxChunkLength ?? DEFAULT_MAX_CHUNK_LENGTH;
10467
+ const chunkOverlap = document.chunking?.chunkOverlap ?? profileOverrides?.options.chunkOverlap ?? defaults?.chunkOverlap ?? DEFAULT_CHUNK_OVERLAP;
10468
+ const minChunkLength = document.chunking?.minChunkLength ?? profileOverrides?.options.minChunkLength ?? defaults?.minChunkLength ?? DEFAULT_MIN_CHUNK_LENGTH;
10469
+ const strategy = document.chunking?.strategy ?? profileOverrides?.options.strategy ?? defaults?.strategy ?? DEFAULT_STRATEGY;
10291
10470
  return {
10292
10471
  chunkOverlap: Math.max(0, Math.min(chunkOverlap, maxChunkLength - 1)),
10293
10472
  maxChunkLength: Math.max(RAG_MIN_CHUNK_LENGTH_FLOOR, maxChunkLength),
@@ -10305,10 +10484,17 @@ var createChunkEntries = (document, format, text, options) => {
10305
10484
  const units = resolveChunkingUnits(text, options);
10306
10485
  return chunkFromUnits(units, options.maxChunkLength, options.chunkOverlap, options.minChunkLength).map((entry) => ({ text: entry }));
10307
10486
  };
10308
- var prepareRAGDocument = (document, defaultChunking) => {
10487
+ var prepareRAGDocument = (document, defaultChunking, chunkingRegistry) => {
10309
10488
  const format = inferFormat(document);
10310
10489
  const normalizedText = normalizeDocumentText(document.text, format);
10311
- const chunking = resolveChunkingOptions(document, defaultChunking);
10490
+ const chunkingProfileOverrides = resolveChunkingProfileOverrides({
10491
+ defaults: defaultChunking,
10492
+ document,
10493
+ format,
10494
+ normalizedText,
10495
+ registry: chunkingRegistry
10496
+ });
10497
+ const chunking = resolveChunkingOptions(document, defaultChunking, chunkingRegistry, format, normalizedText);
10312
10498
  const documentId = document.id?.trim() || slugify(document.source || document.title || normalizedText.slice(0, RAG_DOCUMENT_ID_PREVIEW_LENGTH));
10313
10499
  const title = document.title?.trim() || documentId;
10314
10500
  let sourceExtension = "txt";
@@ -10318,9 +10504,12 @@ var prepareRAGDocument = (document, defaultChunking) => {
10318
10504
  sourceExtension = "html";
10319
10505
  }
10320
10506
  const source = document.source?.trim() || `${documentId}.${sourceExtension}`;
10507
+ const corpusKey = document.corpusKey?.trim() || (typeof document.metadata?.corpusKey === "string" ? document.metadata.corpusKey.trim() : undefined) || undefined;
10321
10508
  const metadata = {
10322
10509
  ...document.metadata ?? {},
10510
+ ...corpusKey ? { corpusKey } : {},
10323
10511
  documentId,
10512
+ ...chunkingProfileOverrides?.name ? { chunkingProfile: chunkingProfileOverrides.name } : {},
10324
10513
  format,
10325
10514
  source,
10326
10515
  title
@@ -10344,6 +10533,7 @@ var prepareRAGDocument = (document, defaultChunking) => {
10344
10533
  const nextChunkId = index + 1 < chunkEntries.length ? `${documentId}:${String(index + 2).padStart(RAG_CHUNK_ID_PAD_LENGTH, "0")}` : undefined;
10345
10534
  return {
10346
10535
  chunkId: `${documentId}:${String(index + 1).padStart(RAG_CHUNK_ID_PAD_LENGTH, "0")}`,
10536
+ ...corpusKey ? { corpusKey } : {},
10347
10537
  metadata: {
10348
10538
  ...metadata,
10349
10539
  chunkCount: chunkEntries.length,
@@ -10366,6 +10556,7 @@ var prepareRAGDocument = (document, defaultChunking) => {
10366
10556
  };
10367
10557
  });
10368
10558
  return {
10559
+ ...corpusKey ? { corpusKey } : {},
10369
10560
  chunks,
10370
10561
  documentId,
10371
10562
  format,
@@ -10375,7 +10566,7 @@ var prepareRAGDocument = (document, defaultChunking) => {
10375
10566
  title
10376
10567
  };
10377
10568
  };
10378
- var prepareRAGDocuments = (input) => input.documents.map((document) => prepareRAGDocument(document, input.defaultChunking));
10569
+ var prepareRAGDocuments = (input) => input.documents.map((document) => prepareRAGDocument(document, input.defaultChunking, input.chunkingRegistry));
10379
10570
  var mergeMetadata = (inputMetadata, extraMetadata, baseMetadata) => ({
10380
10571
  ...baseMetadata ?? {},
10381
10572
  ...inputMetadata ?? {},
@@ -10390,12 +10581,13 @@ var loadRAGDocumentFile = async (input) => {
10390
10581
  chunking: input.chunking,
10391
10582
  contentType: input.contentType,
10392
10583
  data,
10584
+ extractorRegistry: input.extractorRegistry,
10393
10585
  format: input.format,
10394
10586
  metadata: input.metadata,
10395
10587
  path: input.path,
10396
10588
  source: input.source,
10397
10589
  title: input.title
10398
- }, input.extractors);
10590
+ }, input.extractors, input.extractorRegistry);
10399
10591
  return getFirstExtractedDocument(documents, "for file input");
10400
10592
  };
10401
10593
  var loadRAGDocumentFromURL = async (input) => {
@@ -10412,12 +10604,13 @@ var loadRAGDocumentFromURL = async (input) => {
10412
10604
  chunking: input.chunking,
10413
10605
  contentType: input.contentType ?? response.headers.get("content-type") ?? undefined,
10414
10606
  data,
10607
+ extractorRegistry: input.extractorRegistry,
10415
10608
  format: input.format ?? inferFormatFromUrl(url),
10416
10609
  metadata: input.metadata,
10417
10610
  name: basename(new URL(url).pathname),
10418
10611
  source: input.source ?? url,
10419
10612
  title: input.title
10420
- }, input.extractors);
10613
+ }, input.extractors, input.extractorRegistry);
10421
10614
  return getFirstExtractedDocument(documents, "for URL input");
10422
10615
  };
10423
10616
  var loadRAGDocumentsFromUploads = async (input) => {
@@ -10426,12 +10619,13 @@ var loadRAGDocumentsFromUploads = async (input) => {
10426
10619
  chunking: upload.chunking,
10427
10620
  contentType: upload.contentType,
10428
10621
  data: decodeUploadContent(upload),
10622
+ extractorRegistry: input.extractorRegistry,
10429
10623
  format: upload.format,
10430
10624
  metadata: upload.metadata,
10431
10625
  name: upload.name,
10432
10626
  source: upload.source ?? upload.name,
10433
10627
  title: upload.title
10434
- }, input.extractors);
10628
+ }, input.extractors, input.extractorRegistry);
10435
10629
  return loaded.map((document) => ({
10436
10630
  ...document,
10437
10631
  metadata: mergeMetadata(document.metadata, { uploadFile: upload.name }, input.baseMetadata)
@@ -10439,6 +10633,7 @@ var loadRAGDocumentsFromUploads = async (input) => {
10439
10633
  }));
10440
10634
  return {
10441
10635
  defaultChunking: input.defaultChunking,
10636
+ chunkingRegistry: input.chunkingRegistry,
10442
10637
  documents: documents.flat()
10443
10638
  };
10444
10639
  };
@@ -10457,12 +10652,13 @@ var loadRAGDocumentsFromURLs = async (input) => {
10457
10652
  chunking: urlInput.chunking,
10458
10653
  contentType: urlInput.contentType ?? response.headers.get("content-type") ?? undefined,
10459
10654
  data,
10655
+ extractorRegistry: urlInput.extractorRegistry ?? input.extractorRegistry,
10460
10656
  format: urlInput.format ?? inferFormatFromUrl(url),
10461
10657
  metadata: urlInput.metadata,
10462
10658
  name: basename(new URL(url).pathname),
10463
10659
  source: urlInput.source ?? url,
10464
10660
  title: urlInput.title
10465
- }, urlInput.extractors ?? input.extractors);
10661
+ }, urlInput.extractors ?? input.extractors, urlInput.extractorRegistry ?? input.extractorRegistry);
10466
10662
  return loaded.map((document) => ({
10467
10663
  ...document,
10468
10664
  metadata: mergeMetadata(document.metadata, { sourceUrl: urlInput.url }, input.baseMetadata)
@@ -10470,6 +10666,7 @@ var loadRAGDocumentsFromURLs = async (input) => {
10470
10666
  }));
10471
10667
  return {
10472
10668
  defaultChunking: input.defaultChunking,
10669
+ chunkingRegistry: input.chunkingRegistry,
10473
10670
  documents: documents.flat()
10474
10671
  };
10475
10672
  };
@@ -10478,15 +10675,16 @@ var loadRAGDocumentUpload = async (input) => {
10478
10675
  chunking: input.chunking,
10479
10676
  contentType: input.contentType,
10480
10677
  data: decodeUploadContent(input),
10678
+ extractorRegistry: input.extractorRegistry,
10481
10679
  format: input.format,
10482
10680
  metadata: input.metadata,
10483
10681
  name: input.name,
10484
10682
  source: input.source ?? input.name,
10485
10683
  title: input.title
10486
- }, input.extractors);
10684
+ }, input.extractors, input.extractorRegistry);
10487
10685
  return getFirstExtractedDocument(documents, "for upload input");
10488
10686
  };
10489
- var prepareRAGDocumentFile = async (input, defaultChunking) => prepareRAGDocument(await loadRAGDocumentFile(input), defaultChunking);
10687
+ var prepareRAGDocumentFile = async (input, defaultChunking, chunkingRegistry) => prepareRAGDocument(await loadRAGDocumentFile(input), defaultChunking, chunkingRegistry);
10490
10688
  var DEFAULT_DIRECTORY_EXTENSIONS = [
10491
10689
  ".txt",
10492
10690
  ".md",
@@ -10546,7 +10744,7 @@ var buildRAGUpsertInputFromUploads = async (input) => ({
10546
10744
  });
10547
10745
  var loadRAGDocumentsFromDirectory = async (input) => {
10548
10746
  const root = resolve(input.directory);
10549
- const includeExtensions = input.includeExtensions === undefined && input.extractors?.length ? null : new Set((input.includeExtensions ?? DEFAULT_DIRECTORY_EXTENSIONS).map((entry) => entry.startsWith(".") ? entry.toLowerCase() : `.${entry.toLowerCase()}`));
10747
+ const includeExtensions = input.includeExtensions === undefined && (input.extractors?.length || input.extractorRegistry) ? null : new Set((input.includeExtensions ?? DEFAULT_DIRECTORY_EXTENSIONS).map((entry) => entry.startsWith(".") ? entry.toLowerCase() : `.${entry.toLowerCase()}`));
10550
10748
  const files = await collectDirectoryFiles(root, input.recursive !== false, includeExtensions);
10551
10749
  const documents = await Promise.all(files.map(async (path) => {
10552
10750
  const source = relative(root, path).replace(/\\/g, "/");
@@ -10554,13 +10752,14 @@ var loadRAGDocumentsFromDirectory = async (input) => {
10554
10752
  const loaded = await loadExtractedDocuments({
10555
10753
  chunking: input.defaultChunking,
10556
10754
  data,
10755
+ extractorRegistry: input.extractorRegistry,
10557
10756
  metadata: {
10558
10757
  fileName: basename(path),
10559
10758
  relativePath: source
10560
10759
  },
10561
10760
  path,
10562
10761
  source
10563
- }, input.extractors);
10762
+ }, input.extractors, input.extractorRegistry);
10564
10763
  return loaded.map((document) => ({
10565
10764
  ...document,
10566
10765
  metadata: mergeMetadata(document.metadata, undefined, input.baseMetadata)
@@ -10568,6 +10767,7 @@ var loadRAGDocumentsFromDirectory = async (input) => {
10568
10767
  }));
10569
10768
  return {
10570
10769
  defaultChunking: input.defaultChunking,
10770
+ chunkingRegistry: input.chunkingRegistry,
10571
10771
  documents: documents.flat()
10572
10772
  };
10573
10773
  };
@@ -11782,6 +11982,7 @@ var isRAGDocumentUrlArray = (value) => Array.isArray(value) && value.every((entr
11782
11982
  var isRAGDocumentChunkArray = (value) => Array.isArray(value) && value.every((entry) => isRAGDocumentChunk(entry));
11783
11983
  var buildSources2 = (results) => results.map((result) => ({
11784
11984
  chunkId: result.chunkId,
11985
+ corpusKey: result.corpusKey ?? (typeof result.metadata?.corpusKey === "string" ? result.metadata.corpusKey : undefined),
11785
11986
  labels: buildRAGSourceLabels({
11786
11987
  metadata: result.metadata,
11787
11988
  source: result.source,
@@ -11932,6 +12133,8 @@ var buildRAGContextFromQuery = async (config, topK, scoreThreshold, queryText, r
11932
12133
  };
11933
12134
  var ragChat = (config) => {
11934
12135
  const path = config.path ?? DEFAULT_PATH2;
12136
+ const authorizeRAGAction = config.authorizeRAGAction;
12137
+ const resolveRAGAccessScope = config.resolveRAGAccessScope;
11935
12138
  const topK = config.topK ?? DEFAULT_TOP_K3;
11936
12139
  const { scoreThreshold } = config;
11937
12140
  const { extractors } = config;
@@ -11946,6 +12149,13 @@ var ragChat = (config) => {
11946
12149
  const adminActions = [];
11947
12150
  const adminJobs = [];
11948
12151
  const syncJobs = [];
12152
+ const { jobStateStore } = config;
12153
+ const jobHistoryRetention = {
12154
+ maxAdminActions: config.jobHistoryRetention?.maxAdminActions ?? MAX_ADMIN_ACTIONS,
12155
+ maxAdminJobs: config.jobHistoryRetention?.maxAdminJobs ?? MAX_ADMIN_JOBS,
12156
+ maxIngestJobs: config.jobHistoryRetention?.maxIngestJobs ?? MAX_INGEST_JOBS,
12157
+ maxSyncJobs: config.jobHistoryRetention?.maxSyncJobs ?? MAX_ADMIN_JOBS
12158
+ };
11949
12159
  const { searchTraceStore } = config;
11950
12160
  const { searchTraceRetention } = config;
11951
12161
  const { searchTraceRetentionSchedule } = config;
@@ -11964,6 +12174,134 @@ var ragChat = (config) => {
11964
12174
  retention: searchTraceRetention,
11965
12175
  schedule: searchTraceRetentionSchedule
11966
12176
  };
12177
+ let jobStateLoaded = false;
12178
+ let jobStateLoadPromise;
12179
+ const normalizeAuthorizationDecision = (decision) => typeof decision === "boolean" ? { allowed: decision } : decision ?? { allowed: true };
12180
+ const checkAuthorization = async (request, action, resource) => {
12181
+ if (!authorizeRAGAction) {
12182
+ return { allowed: true };
12183
+ }
12184
+ return normalizeAuthorizationDecision(await authorizeRAGAction({
12185
+ action,
12186
+ request,
12187
+ resource
12188
+ }));
12189
+ };
12190
+ const isAuthorized = async (request, action, resource) => (await checkAuthorization(request, action, resource)).allowed;
12191
+ const buildAuthorizationFailure = (decision, fallback = "Forbidden") => ({
12192
+ error: decision.reason ?? fallback,
12193
+ ok: false
12194
+ });
12195
+ const isAccessScopeError = (error) => typeof error === "string" && (error.includes("allowed RAG access scope") || error.includes("Scoped sync-all is not allowed"));
12196
+ const authorizeMutationRoute = async (request, action, input) => {
12197
+ const decision = await checkAuthorization(request, action, input.resource);
12198
+ return decision.allowed ? null : buildAuthorizationFailure(decision, input.fallback);
12199
+ };
12200
+ const normalizeAccessScope = (scope) => {
12201
+ if (!scope) {
12202
+ return;
12203
+ }
12204
+ const normalizeStringArray3 = (values) => {
12205
+ const next = (values ?? []).map((value) => value.trim()).filter((value) => value.length > 0);
12206
+ return next.length > 0 ? [...new Set(next)] : undefined;
12207
+ };
12208
+ const requiredMetadata = scope.requiredMetadata && Object.keys(scope.requiredMetadata).length > 0 ? scope.requiredMetadata : undefined;
12209
+ return {
12210
+ allowedComparisonGroupKeys: normalizeStringArray3(scope.allowedComparisonGroupKeys),
12211
+ allowedCorpusKeys: normalizeStringArray3(scope.allowedCorpusKeys),
12212
+ allowedDocumentIds: normalizeStringArray3(scope.allowedDocumentIds),
12213
+ allowedSourcePrefixes: normalizeStringArray3(scope.allowedSourcePrefixes),
12214
+ allowedSources: normalizeStringArray3(scope.allowedSources),
12215
+ allowedSyncSourceIds: normalizeStringArray3(scope.allowedSyncSourceIds),
12216
+ requiredMetadata
12217
+ };
12218
+ };
12219
+ const loadAccessScope = async (request) => request && resolveRAGAccessScope ? normalizeAccessScope(await resolveRAGAccessScope(request)) : undefined;
12220
+ const matchesRequiredMetadata = (requiredMetadata, metadata) => {
12221
+ if (!requiredMetadata) {
12222
+ return true;
12223
+ }
12224
+ return Object.entries(requiredMetadata).every(([key, value]) => metadata?.[key] === value);
12225
+ };
12226
+ const matchesAccessScope = (scope, input) => {
12227
+ if (!scope) {
12228
+ return true;
12229
+ }
12230
+ const corpusKey = input.corpusKey ?? (typeof input.metadata?.corpusKey === "string" ? input.metadata.corpusKey : undefined);
12231
+ if (scope.allowedCorpusKeys?.length && (!corpusKey || !scope.allowedCorpusKeys.includes(corpusKey))) {
12232
+ return false;
12233
+ }
12234
+ if (scope.allowedDocumentIds?.length && (!input.documentId || !scope.allowedDocumentIds.includes(input.documentId))) {
12235
+ return false;
12236
+ }
12237
+ if (scope.allowedSources?.length && (!input.source || !scope.allowedSources.includes(input.source))) {
12238
+ return false;
12239
+ }
12240
+ if (scope.allowedSourcePrefixes?.length && (!input.source || !scope.allowedSourcePrefixes.some((prefix) => (input.source ?? "").startsWith(prefix)))) {
12241
+ return false;
12242
+ }
12243
+ return matchesRequiredMetadata(scope.requiredMetadata, input.metadata);
12244
+ };
12245
+ const matchesSyncSourceScope = (scope, source) => !scope?.allowedSyncSourceIds?.length || scope.allowedSyncSourceIds.includes(source.id);
12246
+ const isAllowedComparisonGroupKey = (scope, groupKey) => !scope?.allowedComparisonGroupKeys?.length || typeof groupKey === "string" && scope.allowedComparisonGroupKeys.includes(groupKey);
12247
+ const filterByComparisonGroupKey = (scope, records) => scope?.allowedComparisonGroupKeys?.length ? records.filter((record) => isAllowedComparisonGroupKey(scope, record.groupKey)) : records;
12248
+ const persistJobStateIfConfigured = async () => {
12249
+ if (!jobStateStore) {
12250
+ return;
12251
+ }
12252
+ await jobStateStore.save({
12253
+ adminActions: adminActions.slice(0, Math.max(0, jobHistoryRetention.maxAdminActions)),
12254
+ adminJobs: adminJobs.slice(0, Math.max(0, jobHistoryRetention.maxAdminJobs)),
12255
+ ingestJobs: ingestJobs.slice(0, Math.max(0, jobHistoryRetention.maxIngestJobs)),
12256
+ syncJobs: syncJobs.slice(0, Math.max(0, jobHistoryRetention.maxSyncJobs))
12257
+ });
12258
+ };
12259
+ const recoverIngestJob = (job, recoveredAt) => job.status === "running" ? {
12260
+ ...job,
12261
+ elapsedMs: Math.max(0, recoveredAt - job.startedAt),
12262
+ error: job.error ?? "Interrupted before completion during recovery",
12263
+ finishedAt: recoveredAt,
12264
+ status: "failed"
12265
+ } : job;
12266
+ const recoverAdminJob = (job, recoveredAt) => job.status === "running" ? {
12267
+ ...job,
12268
+ elapsedMs: Math.max(0, recoveredAt - job.startedAt),
12269
+ error: job.error ?? "Interrupted before completion during recovery",
12270
+ finishedAt: recoveredAt,
12271
+ status: "failed"
12272
+ } : job;
12273
+ const ensureJobStateLoaded = async () => {
12274
+ if (jobStateLoaded) {
12275
+ return;
12276
+ }
12277
+ if (jobStateLoadPromise) {
12278
+ await jobStateLoadPromise;
12279
+ return;
12280
+ }
12281
+ jobStateLoadPromise = (async () => {
12282
+ if (!jobStateStore) {
12283
+ jobStateLoaded = true;
12284
+ return;
12285
+ }
12286
+ const loaded = await jobStateStore.load();
12287
+ const recoveredAt = Date.now();
12288
+ const nextIngestJobs = (loaded?.ingestJobs ?? []).map((job) => recoverIngestJob(job, recoveredAt)).slice(0, Math.max(0, jobHistoryRetention.maxIngestJobs));
12289
+ const nextAdminActions = (loaded?.adminActions ?? []).slice(0, Math.max(0, jobHistoryRetention.maxAdminActions));
12290
+ const nextAdminJobs = (loaded?.adminJobs ?? []).map((job) => recoverAdminJob(job, recoveredAt)).slice(0, Math.max(0, jobHistoryRetention.maxAdminJobs));
12291
+ const nextSyncJobs = (loaded?.syncJobs ?? []).map((job) => recoverAdminJob(job, recoveredAt)).slice(0, Math.max(0, jobHistoryRetention.maxSyncJobs));
12292
+ adminActions.splice(0, adminActions.length, ...nextAdminActions);
12293
+ ingestJobs.splice(0, ingestJobs.length, ...nextIngestJobs);
12294
+ adminJobs.splice(0, adminJobs.length, ...nextAdminJobs);
12295
+ syncJobs.splice(0, syncJobs.length, ...nextSyncJobs);
12296
+ jobStateLoaded = true;
12297
+ await persistJobStateIfConfigured();
12298
+ })();
12299
+ try {
12300
+ await jobStateLoadPromise;
12301
+ } finally {
12302
+ jobStateLoadPromise = undefined;
12303
+ }
12304
+ };
11967
12305
  const createIngestJob = (inputKind, requestedCount) => {
11968
12306
  const job = {
11969
12307
  id: generateId(),
@@ -11973,9 +12311,10 @@ var ragChat = (config) => {
11973
12311
  status: "running"
11974
12312
  };
11975
12313
  ingestJobs.unshift(job);
11976
- if (ingestJobs.length > MAX_INGEST_JOBS) {
11977
- ingestJobs.length = MAX_INGEST_JOBS;
12314
+ if (ingestJobs.length > jobHistoryRetention.maxIngestJobs) {
12315
+ ingestJobs.length = jobHistoryRetention.maxIngestJobs;
11978
12316
  }
12317
+ persistJobStateIfConfigured();
11979
12318
  return job;
11980
12319
  };
11981
12320
  const completeIngestJob = (job, input) => {
@@ -11986,6 +12325,7 @@ var ragChat = (config) => {
11986
12325
  job.chunkCount = input.chunkCount;
11987
12326
  job.documentCount = input.documentCount;
11988
12327
  job.extractorNames = input.extractorNames;
12328
+ persistJobStateIfConfigured();
11989
12329
  };
11990
12330
  const failIngestJob = (job, error, extractorNames) => {
11991
12331
  const finishedAt = Date.now();
@@ -11994,6 +12334,7 @@ var ragChat = (config) => {
11994
12334
  job.elapsedMs = finishedAt - job.startedAt;
11995
12335
  job.error = error;
11996
12336
  job.extractorNames = extractorNames;
12337
+ persistJobStateIfConfigured();
11997
12338
  };
11998
12339
  const createAdminAction = (action, documentId, target) => {
11999
12340
  const record = {
@@ -12005,9 +12346,10 @@ var ragChat = (config) => {
12005
12346
  target
12006
12347
  };
12007
12348
  adminActions.unshift(record);
12008
- if (adminActions.length > MAX_ADMIN_ACTIONS) {
12009
- adminActions.length = MAX_ADMIN_ACTIONS;
12349
+ if (adminActions.length > jobHistoryRetention.maxAdminActions) {
12350
+ adminActions.length = jobHistoryRetention.maxAdminActions;
12010
12351
  }
12352
+ persistJobStateIfConfigured();
12011
12353
  return record;
12012
12354
  };
12013
12355
  const createAdminJob = (action, target, bucket = adminJobs) => {
@@ -12019,9 +12361,11 @@ var ragChat = (config) => {
12019
12361
  target
12020
12362
  };
12021
12363
  bucket.unshift(job);
12022
- if (bucket.length > MAX_ADMIN_JOBS) {
12023
- bucket.length = MAX_ADMIN_JOBS;
12364
+ const maxJobs = bucket === syncJobs ? jobHistoryRetention.maxSyncJobs : jobHistoryRetention.maxAdminJobs;
12365
+ if (bucket.length > maxJobs) {
12366
+ bucket.length = maxJobs;
12024
12367
  }
12368
+ persistJobStateIfConfigured();
12025
12369
  return job;
12026
12370
  };
12027
12371
  const completeAdminAction = (record) => {
@@ -12029,6 +12373,7 @@ var ragChat = (config) => {
12029
12373
  record.status = "completed";
12030
12374
  record.finishedAt = finishedAt;
12031
12375
  record.elapsedMs = finishedAt - record.startedAt;
12376
+ persistJobStateIfConfigured();
12032
12377
  };
12033
12378
  const failAdminAction = (record, error) => {
12034
12379
  const finishedAt = Date.now();
@@ -12036,12 +12381,14 @@ var ragChat = (config) => {
12036
12381
  record.finishedAt = finishedAt;
12037
12382
  record.elapsedMs = finishedAt - record.startedAt;
12038
12383
  record.error = error;
12384
+ persistJobStateIfConfigured();
12039
12385
  };
12040
12386
  const completeAdminJob = (job) => {
12041
12387
  const finishedAt = Date.now();
12042
12388
  job.status = "completed";
12043
12389
  job.finishedAt = finishedAt;
12044
12390
  job.elapsedMs = finishedAt - job.startedAt;
12391
+ persistJobStateIfConfigured();
12045
12392
  };
12046
12393
  const failAdminJob = (job, error) => {
12047
12394
  const finishedAt = Date.now();
@@ -12049,8 +12396,10 @@ var ragChat = (config) => {
12049
12396
  job.finishedAt = finishedAt;
12050
12397
  job.elapsedMs = finishedAt - job.startedAt;
12051
12398
  job.error = error;
12399
+ persistJobStateIfConfigured();
12052
12400
  };
12053
12401
  const runSearchTracePrune = async (input, trigger = "manual") => {
12402
+ await ensureJobStateLoaded();
12054
12403
  if (!searchTraceStore) {
12055
12404
  throw new Error("RAG search trace store is not configured");
12056
12405
  }
@@ -12138,11 +12487,12 @@ var ragChat = (config) => {
12138
12487
  throw error;
12139
12488
  }
12140
12489
  };
12141
- const buildSyncSources = async () => {
12490
+ const buildSyncSources = async (scope) => {
12142
12491
  if (!indexManager?.listSyncSources) {
12143
12492
  return [];
12144
12493
  }
12145
- return await indexManager.listSyncSources();
12494
+ const sources = await indexManager.listSyncSources();
12495
+ return sources.filter((source) => matchesSyncSourceScope(scope, source));
12146
12496
  };
12147
12497
  const toHTMXResponse = (html, status, extraHeaders) => new Response(html, {
12148
12498
  headers: {
@@ -12306,6 +12656,7 @@ var ragChat = (config) => {
12306
12656
  return null;
12307
12657
  }
12308
12658
  return {
12659
+ corpusKey: getStringProperty(candidate, "corpusKey") ?? undefined,
12309
12660
  filter: caseFilter,
12310
12661
  id: getStringProperty(candidate, "id") ?? `case-${caseIndex + 1}`,
12311
12662
  retrieval: parsedCaseRetrieval === undefined || parsedCaseRetrieval === null ? undefined : parsedCaseRetrieval,
@@ -12343,7 +12694,7 @@ var ragChat = (config) => {
12343
12694
  scoreThreshold: typeof getNumberProperty(body, "scoreThreshold") === "number" ? getNumberProperty(body, "scoreThreshold") : undefined
12344
12695
  };
12345
12696
  };
12346
- const handleEvaluate = async (body) => {
12697
+ const handleEvaluate = async (body, request) => {
12347
12698
  const input = toRAGEvaluationInput(body);
12348
12699
  if (!input) {
12349
12700
  return {
@@ -12351,6 +12702,17 @@ var ragChat = (config) => {
12351
12702
  ok: false
12352
12703
  };
12353
12704
  }
12705
+ const accessScope = await loadAccessScope(request);
12706
+ for (const evaluationCase of input.cases) {
12707
+ if (evaluationCase.corpusKey && !matchesAccessScope(accessScope, {
12708
+ corpusKey: evaluationCase.corpusKey
12709
+ }) || (evaluationCase.expectedDocumentIds ?? []).some((documentId) => !matchesAccessScope(accessScope, { documentId })) || (evaluationCase.expectedSources ?? []).some((source) => !matchesAccessScope(accessScope, { source }))) {
12710
+ return {
12711
+ error: "Evaluation case is outside the allowed RAG access scope",
12712
+ ok: false
12713
+ };
12714
+ }
12715
+ }
12354
12716
  const collection = resolveCollection();
12355
12717
  if (!collection) {
12356
12718
  return {
@@ -12853,7 +13215,7 @@ var ragChat = (config) => {
12853
13215
  });
12854
13216
  return incidents.find((entry) => entry.id === incidentId);
12855
13217
  };
12856
- const handleEvaluateRetrievals = async (body) => {
13218
+ const handleEvaluateRetrievals = async (body, request) => {
12857
13219
  const input = toRAGRetrievalComparisonRequest(body);
12858
13220
  if (!input) {
12859
13221
  return {
@@ -12861,6 +13223,23 @@ var ragChat = (config) => {
12861
13223
  ok: false
12862
13224
  };
12863
13225
  }
13226
+ const accessScope = await loadAccessScope(request);
13227
+ if (!isAllowedComparisonGroupKey(accessScope, input.groupKey)) {
13228
+ return {
13229
+ error: "Retrieval comparison group is outside the allowed RAG access scope",
13230
+ ok: false
13231
+ };
13232
+ }
13233
+ for (const evaluationCase of input.cases) {
13234
+ if (evaluationCase.corpusKey && !matchesAccessScope(accessScope, {
13235
+ corpusKey: evaluationCase.corpusKey
13236
+ }) || (evaluationCase.expectedDocumentIds ?? []).some((documentId) => !matchesAccessScope(accessScope, { documentId })) || (evaluationCase.expectedSources ?? []).some((source) => !matchesAccessScope(accessScope, { source }))) {
13237
+ return {
13238
+ error: "Retrieval comparison case is outside the allowed RAG access scope",
13239
+ ok: false
13240
+ };
13241
+ }
13242
+ }
12864
13243
  const collection = resolveCollection();
12865
13244
  if (!collection) {
12866
13245
  return {
@@ -12907,6 +13286,7 @@ var ragChat = (config) => {
12907
13286
  await persistRAGRetrievalComparisonRun({
12908
13287
  run: {
12909
13288
  comparison,
13289
+ corpusKeys: comparison.corpusKeys,
12910
13290
  decisionSummary,
12911
13291
  elapsedMs: finishedAt - startedAt,
12912
13292
  finishedAt,
@@ -12930,15 +13310,23 @@ var ragChat = (config) => {
12930
13310
  ok: true
12931
13311
  };
12932
13312
  };
12933
- const handleRetrievalComparisonHistory = async (queryInput) => {
13313
+ const handleRetrievalComparisonHistory = async (queryInput, request) => {
12934
13314
  if (!retrievalComparisonHistoryStore) {
12935
13315
  return {
12936
13316
  error: "RAG retrieval comparison history store is not configured",
12937
13317
  ok: false
12938
13318
  };
12939
13319
  }
13320
+ const accessScope = await loadAccessScope(request);
13321
+ const groupKey = getStringProperty(queryInput, "groupKey");
13322
+ if (!isAllowedComparisonGroupKey(accessScope, groupKey)) {
13323
+ return {
13324
+ error: "Retrieval comparison group is outside the allowed RAG access scope",
13325
+ ok: false
13326
+ };
13327
+ }
12940
13328
  const runs = await loadRAGRetrievalComparisonHistory({
12941
- groupKey: getStringProperty(queryInput, "groupKey"),
13329
+ groupKey,
12942
13330
  label: getStringProperty(queryInput, "label"),
12943
13331
  limit: getIntegerLikeProperty(queryInput, "limit"),
12944
13332
  store: retrievalComparisonHistoryStore,
@@ -12948,25 +13336,33 @@ var ragChat = (config) => {
12948
13336
  });
12949
13337
  return {
12950
13338
  ok: true,
12951
- runs
13339
+ runs: filterByComparisonGroupKey(accessScope, runs)
12952
13340
  };
12953
13341
  };
12954
- const handleRetrievalBaselineList = async (queryInput) => {
13342
+ const handleRetrievalBaselineList = async (queryInput, request) => {
12955
13343
  if (!retrievalBaselineStore) {
12956
13344
  return {
12957
13345
  error: "RAG retrieval baseline store is not configured",
12958
13346
  ok: false
12959
13347
  };
12960
13348
  }
13349
+ const accessScope = await loadAccessScope(request);
13350
+ const groupKey = getStringProperty(queryInput, "groupKey");
13351
+ if (!isAllowedComparisonGroupKey(accessScope, groupKey)) {
13352
+ return {
13353
+ error: "Retrieval baseline group is outside the allowed RAG access scope",
13354
+ ok: false
13355
+ };
13356
+ }
12961
13357
  const baselines = await loadRAGRetrievalBaselines({
12962
- groupKey: getStringProperty(queryInput, "groupKey"),
13358
+ groupKey,
12963
13359
  limit: getIntegerLikeProperty(queryInput, "limit"),
12964
13360
  status: getStringProperty(queryInput, "status") === "active" || getStringProperty(queryInput, "status") === "superseded" ? getStringProperty(queryInput, "status") : undefined,
12965
13361
  store: retrievalBaselineStore,
12966
13362
  tag: getStringProperty(queryInput, "tag")
12967
13363
  });
12968
13364
  return {
12969
- baselines,
13365
+ baselines: filterByComparisonGroupKey(accessScope, baselines),
12970
13366
  ok: true
12971
13367
  };
12972
13368
  };
@@ -13679,7 +14075,7 @@ var ragChat = (config) => {
13679
14075
  });
13680
14076
  return baseline;
13681
14077
  };
13682
- const handlePromoteRetrievalBaseline = async (body) => {
14078
+ const handlePromoteRetrievalBaseline = async (body, request) => {
13683
14079
  if (!retrievalBaselineStore) {
13684
14080
  return {
13685
14081
  error: "RAG retrieval baseline store is not configured",
@@ -13776,7 +14172,7 @@ var ragChat = (config) => {
13776
14172
  };
13777
14173
  }
13778
14174
  };
13779
- const handlePromoteRetrievalBaselineFromRun = async (body) => {
14175
+ const handlePromoteRetrievalBaselineFromRun = async (body, request) => {
13780
14176
  if (!retrievalBaselineStore || !retrievalComparisonHistoryStore) {
13781
14177
  return {
13782
14178
  error: "RAG retrieval baseline store and retrieval comparison history store are required",
@@ -13876,7 +14272,7 @@ var ragChat = (config) => {
13876
14272
  };
13877
14273
  }
13878
14274
  };
13879
- const handleRevertRetrievalBaseline = async (body) => {
14275
+ const handleRevertRetrievalBaseline = async (body, request) => {
13880
14276
  if (!retrievalBaselineStore) {
13881
14277
  return {
13882
14278
  error: "RAG retrieval baseline store is not configured",
@@ -13951,24 +14347,32 @@ var ragChat = (config) => {
13951
14347
  };
13952
14348
  }
13953
14349
  };
13954
- const handleRetrievalReleaseDecisionList = async (queryInput) => {
14350
+ const handleRetrievalReleaseDecisionList = async (queryInput, request) => {
13955
14351
  if (!config.retrievalReleaseDecisionStore) {
13956
14352
  return {
13957
14353
  error: "RAG retrieval release decision store is not configured",
13958
14354
  ok: false
13959
14355
  };
13960
14356
  }
14357
+ const accessScope = await loadAccessScope(request);
14358
+ const groupKey = getStringProperty(queryInput, "groupKey");
14359
+ if (!isAllowedComparisonGroupKey(accessScope, groupKey)) {
14360
+ return {
14361
+ error: "Retrieval release decision group is outside the allowed RAG access scope",
14362
+ ok: false
14363
+ };
14364
+ }
13961
14365
  const kind = getStringProperty(queryInput, "kind");
13962
14366
  const targetRolloutLabel = getStringProperty(queryInput, "targetRolloutLabel");
13963
14367
  const freshnessStatusFilter = getStringProperty(queryInput, "freshnessStatus");
13964
14368
  const decisions = await loadRAGRetrievalReleaseDecisions({
13965
- groupKey: getStringProperty(queryInput, "groupKey"),
14369
+ groupKey,
13966
14370
  kind: kind === "approve" || kind === "promote" || kind === "reject" || kind === "revert" ? kind : undefined,
13967
14371
  limit: getIntegerLikeProperty(queryInput, "limit"),
13968
14372
  store: config.retrievalReleaseDecisionStore
13969
14373
  });
13970
14374
  return {
13971
- decisions: decisions.map((decision) => ({
14375
+ decisions: filterByComparisonGroupKey(accessScope, decisions).map((decision) => ({
13972
14376
  ...decision,
13973
14377
  ...getDecisionFreshness({ record: decision })
13974
14378
  })).filter((decision) => {
@@ -13986,7 +14390,7 @@ var ragChat = (config) => {
13986
14390
  ok: true
13987
14391
  };
13988
14392
  };
13989
- const handleRetrievalReleaseDecisionAction = async (body, kind) => {
14393
+ const handleRetrievalReleaseDecisionAction = async (body, kind, request) => {
13990
14394
  if (!retrievalComparisonHistoryStore || !config.retrievalReleaseDecisionStore) {
13991
14395
  return {
13992
14396
  error: "RAG retrieval comparison history store and release decision store are required",
@@ -15313,7 +15717,7 @@ var ragChat = (config) => {
15313
15717
  return { error: message, ok: false };
15314
15718
  }
15315
15719
  };
15316
- const handleSearch = async (body) => {
15720
+ const handleSearch = async (body, request) => {
15317
15721
  if (!isObjectRecord2(body)) {
15318
15722
  return { error: "Invalid payload", ok: false };
15319
15723
  }
@@ -15325,6 +15729,7 @@ var ragChat = (config) => {
15325
15729
  };
15326
15730
  }
15327
15731
  const collection = resolveCollection();
15732
+ const accessScope = await loadAccessScope(request);
15328
15733
  if (!collection) {
15329
15734
  return { error: "RAG collection is not configured", ok: false };
15330
15735
  }
@@ -15367,14 +15772,28 @@ var ragChat = (config) => {
15367
15772
  trace: result.trace
15368
15773
  });
15369
15774
  }
15775
+ const scopedResults = buildSources2(result.results).filter((entry) => matchesAccessScope(accessScope, {
15776
+ corpusKey: entry.corpusKey,
15777
+ documentId: typeof entry.metadata?.documentId === "string" ? entry.metadata.documentId : entry.chunkId.split(":")[0],
15778
+ metadata: entry.metadata,
15779
+ source: entry.source
15780
+ }));
15370
15781
  return {
15371
15782
  ok: true,
15372
- results: buildSources2(result.results),
15783
+ results: scopedResults,
15373
15784
  trace: result.trace
15374
15785
  };
15375
15786
  }
15376
15787
  const results = await collection.search(input);
15377
- return { ok: true, results: buildSources2(results) };
15788
+ return {
15789
+ ok: true,
15790
+ results: buildSources2(results).filter((entry) => matchesAccessScope(accessScope, {
15791
+ corpusKey: entry.corpusKey,
15792
+ documentId: typeof entry.metadata?.documentId === "string" ? entry.metadata.documentId : entry.chunkId.split(":")[0],
15793
+ metadata: entry.metadata,
15794
+ source: entry.source
15795
+ }))
15796
+ };
15378
15797
  };
15379
15798
  const handleTraceHistory = async (queryInput) => {
15380
15799
  if (!searchTraceStore) {
@@ -15520,7 +15939,10 @@ var ragChat = (config) => {
15520
15939
  let inspectedChunks = 0;
15521
15940
  let documentsWithSourceLabels = 0;
15522
15941
  let chunksWithSourceLabels = 0;
15942
+ const corpusKeys = new Map;
15523
15943
  const sourceNativeKinds = new Map;
15944
+ const extractorRegistryMatches = new Map;
15945
+ const chunkingProfiles = new Map;
15524
15946
  const sampleDocuments = [];
15525
15947
  const sampleChunks = [];
15526
15948
  let oldestDocumentAgeMs;
@@ -15572,11 +15994,26 @@ var ragChat = (config) => {
15572
15994
  documentsWithSourceLabels += 1;
15573
15995
  }
15574
15996
  const documentSourceNativeKind = typeof document.metadata?.sourceNativeKind === "string" ? document.metadata.sourceNativeKind : undefined;
15997
+ const documentCorpusKey = document.corpusKey ?? (typeof document.metadata?.corpusKey === "string" ? document.metadata.corpusKey : undefined);
15998
+ const documentExtractorRegistryMatch = typeof document.metadata?.extractorRegistryMatch === "string" ? document.metadata.extractorRegistryMatch : undefined;
15999
+ const documentChunkingProfile = typeof document.metadata?.chunkingProfile === "string" ? document.metadata.chunkingProfile : undefined;
15575
16000
  if (documentSourceNativeKind) {
15576
16001
  sourceNativeKinds.set(documentSourceNativeKind, (sourceNativeKinds.get(documentSourceNativeKind) ?? 0) + 1);
15577
16002
  }
15578
- if (sampleDocuments.length < 5 && (documentLabels || documentSourceNativeKind)) {
16003
+ if (documentCorpusKey) {
16004
+ corpusKeys.set(documentCorpusKey, (corpusKeys.get(documentCorpusKey) ?? 0) + 1);
16005
+ }
16006
+ if (documentExtractorRegistryMatch) {
16007
+ extractorRegistryMatches.set(documentExtractorRegistryMatch, (extractorRegistryMatches.get(documentExtractorRegistryMatch) ?? 0) + 1);
16008
+ }
16009
+ if (documentChunkingProfile) {
16010
+ chunkingProfiles.set(documentChunkingProfile, (chunkingProfiles.get(documentChunkingProfile) ?? 0) + 1);
16011
+ }
16012
+ if (sampleDocuments.length < 5 && (documentCorpusKey || documentLabels || documentSourceNativeKind || documentExtractorRegistryMatch || documentChunkingProfile)) {
15579
16013
  sampleDocuments.push({
16014
+ corpusKey: documentCorpusKey,
16015
+ chunkingProfile: documentChunkingProfile,
16016
+ extractorRegistryMatch: documentExtractorRegistryMatch,
15580
16017
  id: document.id,
15581
16018
  labels: documentLabels,
15582
16019
  source: document.source,
@@ -15602,13 +16039,28 @@ var ragChat = (config) => {
15602
16039
  chunksWithSourceLabels += 1;
15603
16040
  }
15604
16041
  const chunkSourceNativeKind = typeof chunk.metadata?.sourceNativeKind === "string" ? chunk.metadata.sourceNativeKind : undefined;
16042
+ const chunkCorpusKey = chunk.corpusKey ?? (typeof chunk.metadata?.corpusKey === "string" ? chunk.metadata.corpusKey : documentCorpusKey);
16043
+ const chunkExtractorRegistryMatch = typeof chunk.metadata?.extractorRegistryMatch === "string" ? chunk.metadata.extractorRegistryMatch : undefined;
16044
+ const chunkChunkingProfile = typeof chunk.metadata?.chunkingProfile === "string" ? chunk.metadata.chunkingProfile : undefined;
15605
16045
  if (chunkSourceNativeKind) {
15606
16046
  sourceNativeKinds.set(chunkSourceNativeKind, (sourceNativeKinds.get(chunkSourceNativeKind) ?? 0) + 1);
15607
16047
  }
15608
- if (sampleChunks.length < 8 && (chunkLabels || chunkSourceNativeKind)) {
16048
+ if (chunkCorpusKey) {
16049
+ corpusKeys.set(chunkCorpusKey, (corpusKeys.get(chunkCorpusKey) ?? 0) + 1);
16050
+ }
16051
+ if (chunkExtractorRegistryMatch) {
16052
+ extractorRegistryMatches.set(chunkExtractorRegistryMatch, (extractorRegistryMatches.get(chunkExtractorRegistryMatch) ?? 0) + 1);
16053
+ }
16054
+ if (chunkChunkingProfile) {
16055
+ chunkingProfiles.set(chunkChunkingProfile, (chunkingProfiles.get(chunkChunkingProfile) ?? 0) + 1);
16056
+ }
16057
+ if (sampleChunks.length < 8 && (chunkCorpusKey || chunkLabels || chunkSourceNativeKind || chunkExtractorRegistryMatch || chunkChunkingProfile)) {
15609
16058
  sampleChunks.push({
15610
16059
  chunkId: chunk.chunkId,
16060
+ chunkingProfile: chunkChunkingProfile,
16061
+ corpusKey: chunkCorpusKey,
15611
16062
  documentId: document.id,
16063
+ extractorRegistryMatch: chunkExtractorRegistryMatch,
15612
16064
  labels: chunkLabels,
15613
16065
  source: chunk.source ?? preview.document.source,
15614
16066
  sourceNativeKind: chunkSourceNativeKind
@@ -15671,8 +16123,11 @@ var ragChat = (config) => {
15671
16123
  inspectedChunks,
15672
16124
  inspectedDocuments,
15673
16125
  inspection: {
16126
+ chunkingProfiles: Object.fromEntries(chunkingProfiles.entries()),
16127
+ corpusKeys: Object.fromEntries(corpusKeys.entries()),
15674
16128
  chunksWithSourceLabels,
15675
16129
  documentsWithSourceLabels,
16130
+ extractorRegistryMatches: Object.fromEntries(extractorRegistryMatches.entries()),
15676
16131
  sampleChunks,
15677
16132
  sampleDocuments,
15678
16133
  sourceNativeKinds: Object.fromEntries(sourceNativeKinds.entries())
@@ -15695,28 +16150,34 @@ var ragChat = (config) => {
15695
16150
  providerName: typeof config.provider === "function" ? config.readinessProviderName : undefined,
15696
16151
  rerankerConfigured: Boolean(config.rerank ?? config.collection)
15697
16152
  });
15698
- const buildAdminCapabilities = () => ({
15699
- canClearIndex: Boolean(ragStore?.clear),
15700
- canCreateDocument: Boolean(indexManager?.createDocument),
15701
- canDeleteDocument: Boolean(indexManager?.deleteDocument),
15702
- canListSyncSources: Boolean(indexManager?.listSyncSources),
15703
- canManageRetrievalBaselines: Boolean(retrievalBaselineStore),
15704
- canPruneSearchTraces: Boolean(searchTraceStore),
15705
- canReindexDocument: Boolean(indexManager?.reindexDocument),
15706
- canReindexSource: Boolean(indexManager?.reindexSource),
15707
- canReseed: Boolean(indexManager?.reseed),
15708
- canReset: Boolean(indexManager?.reset),
15709
- canSyncAllSources: Boolean(indexManager?.syncAllSources),
15710
- canSyncSource: Boolean(indexManager?.syncSource)
16153
+ const buildAdminCapabilities = async (request) => ({
16154
+ canClearIndex: Boolean(ragStore?.clear) && (request ? await isAuthorized(request, "clear_index") : true),
16155
+ canCreateDocument: Boolean(indexManager?.createDocument) && (request ? await isAuthorized(request, "create_document") : true),
16156
+ canDeleteDocument: Boolean(indexManager?.deleteDocument) && (request ? await isAuthorized(request, "delete_document") : true),
16157
+ canListSyncSources: Boolean(indexManager?.listSyncSources) && (request ? await isAuthorized(request, "list_sync_sources") : true),
16158
+ canManageRetrievalBaselines: Boolean(retrievalBaselineStore) && (request ? await isAuthorized(request, "manage_retrieval_admin") : true),
16159
+ canPruneSearchTraces: Boolean(searchTraceStore) && (request ? await isAuthorized(request, "prune_search_traces") : true),
16160
+ canReindexDocument: Boolean(indexManager?.reindexDocument) && (request ? await isAuthorized(request, "reindex_document") : true),
16161
+ canReindexSource: Boolean(indexManager?.reindexSource) && (request ? await isAuthorized(request, "reindex_source") : true),
16162
+ canReseed: Boolean(indexManager?.reseed) && (request ? await isAuthorized(request, "reseed") : true),
16163
+ canReset: Boolean(indexManager?.reset) && (request ? await isAuthorized(request, "reset") : true),
16164
+ canSyncAllSources: Boolean(indexManager?.syncAllSources) && (request ? await isAuthorized(request, "sync_all_sources") : true),
16165
+ canSyncSource: Boolean(indexManager?.syncSource) && (request ? await isAuthorized(request, "sync_source") : true)
15711
16166
  });
15712
- const buildOperationsPayload = async () => {
16167
+ const buildOperationsPayload = async (request) => {
16168
+ const accessScope = await loadAccessScope(request);
15713
16169
  const collection = config.collection ?? (ragStore ? createRAGCollection({
15714
16170
  defaultModel: config.embeddingModel,
15715
16171
  defaultTopK: topK,
15716
16172
  embedding: config.embedding,
15717
16173
  store: ragStore
15718
16174
  }) : null);
15719
- const indexedDocuments = indexManager ? await indexManager.listDocuments({}) : [];
16175
+ const indexedDocuments = indexManager ? (await indexManager.listDocuments({})).filter((document) => matchesAccessScope(accessScope, {
16176
+ corpusKey: document.corpusKey,
16177
+ documentId: document.id,
16178
+ metadata: document.metadata,
16179
+ source: document.source
16180
+ })) : [];
15720
16181
  const traceStats = searchTraceStore ? await summarizeRAGSearchTraceStore({
15721
16182
  store: searchTraceStore
15722
16183
  }) : undefined;
@@ -16707,7 +17168,7 @@ var ragChat = (config) => {
16707
17168
  searchTraceRuntime.stats = traceStats;
16708
17169
  searchTraceRuntime.recentRuns = recentTraceRuns;
16709
17170
  return {
16710
- admin: buildAdminCapabilities(),
17171
+ admin: await buildAdminCapabilities(request),
16711
17172
  adminActions: [...adminActions],
16712
17173
  adminJobs: [...adminJobs, ...syncJobs].sort((left, right) => right.startedAt - left.startedAt),
16713
17174
  capabilities: collection?.getCapabilities?.(),
@@ -16786,27 +17247,27 @@ var ragChat = (config) => {
16786
17247
  stats: traceStats
16787
17248
  },
16788
17249
  status: collection?.getStatus?.(),
16789
- syncSources: await buildSyncSources()
17250
+ syncSources: await buildSyncSources(accessScope)
16790
17251
  };
16791
17252
  };
16792
- const handleStatus = async () => buildOperationsPayload();
16793
- const handleRetrievalReleaseStatus = async () => {
16794
- const result = await buildOperationsPayload();
17253
+ const handleStatus = async (request) => buildOperationsPayload(request);
17254
+ const handleRetrievalReleaseStatus = async (request) => {
17255
+ const result = await buildOperationsPayload(request);
16795
17256
  return {
16796
17257
  ok: true,
16797
17258
  retrievalComparisons: result.retrievalComparisons
16798
17259
  };
16799
17260
  };
16800
- const handleRetrievalReleaseDriftStatus = async () => {
16801
- const result = await buildOperationsPayload();
17261
+ const handleRetrievalReleaseDriftStatus = async (request) => {
17262
+ const result = await buildOperationsPayload(request);
16802
17263
  return {
16803
17264
  handoffDriftCountsByLane: result.retrievalComparisons?.handoffDriftCountsByLane,
16804
17265
  handoffDriftRollups: result.retrievalComparisons?.handoffDriftRollups,
16805
17266
  ok: true
16806
17267
  };
16807
17268
  };
16808
- const handleRetrievalLaneHandoffIncidentStatus = async () => {
16809
- const result = await buildOperationsPayload();
17269
+ const handleRetrievalLaneHandoffIncidentStatus = async (request) => {
17270
+ const result = await buildOperationsPayload(request);
16810
17271
  const incidents = result.retrievalComparisons?.recentLaneHandoffIncidents;
16811
17272
  return {
16812
17273
  freshnessWindows: result.retrievalComparisons?.handoffFreshnessWindows,
@@ -16816,8 +17277,8 @@ var ragChat = (config) => {
16816
17277
  ok: true
16817
17278
  };
16818
17279
  };
16819
- const handleRetrievalLaneHandoffStatus = async () => {
16820
- const result = await buildOperationsPayload();
17280
+ const handleRetrievalLaneHandoffStatus = async (request) => {
17281
+ const result = await buildOperationsPayload(request);
16821
17282
  const incidents = result.retrievalComparisons?.recentLaneHandoffIncidents;
16822
17283
  return {
16823
17284
  autoComplete: result.retrievalComparisons?.handoffAutoComplete,
@@ -16830,7 +17291,7 @@ var ragChat = (config) => {
16830
17291
  ok: true
16831
17292
  };
16832
17293
  };
16833
- const handleOps = async () => buildOperationsPayload();
17294
+ const handleOps = async (request) => buildOperationsPayload(request);
16834
17295
  if (searchTraceStore && searchTraceRetention && searchTraceRetentionSchedule) {
16835
17296
  const runScheduledSearchTracePrune = async () => {
16836
17297
  searchTraceRuntime.nextScheduledAt = Date.now() + searchTraceRetentionSchedule.intervalMs;
@@ -16848,14 +17309,20 @@ var ragChat = (config) => {
16848
17309
  }, searchTraceRetentionSchedule.intervalMs);
16849
17310
  timer.unref?.();
16850
17311
  }
16851
- const handleDocuments = async (kind) => {
17312
+ const handleDocuments = async (kind, request) => {
16852
17313
  if (!indexManager) {
16853
17314
  return {
16854
17315
  error: "RAG index document management is not configured",
16855
17316
  ok: false
16856
17317
  };
16857
17318
  }
16858
- const documents = await indexManager.listDocuments({ kind });
17319
+ const accessScope = await loadAccessScope(request);
17320
+ const documents = (await indexManager.listDocuments({ kind })).filter((document) => matchesAccessScope(accessScope, {
17321
+ corpusKey: document.corpusKey,
17322
+ documentId: document.id,
17323
+ metadata: document.metadata,
17324
+ source: document.source
17325
+ }));
16859
17326
  return {
16860
17327
  documents: documents.map((document) => ({
16861
17328
  ...document,
@@ -16868,7 +17335,7 @@ var ragChat = (config) => {
16868
17335
  ok: true
16869
17336
  };
16870
17337
  };
16871
- const handleCreateDocument = async (body) => {
17338
+ const handleCreateDocument = async (body, request) => {
16872
17339
  if (!indexManager?.createDocument) {
16873
17340
  return {
16874
17341
  error: "RAG document creation is not configured",
@@ -16887,6 +17354,18 @@ var ragChat = (config) => {
16887
17354
  ok: false
16888
17355
  };
16889
17356
  }
17357
+ const accessScope = await loadAccessScope(request);
17358
+ if (!matchesAccessScope(accessScope, {
17359
+ documentId: body.id,
17360
+ corpusKey: body.corpusKey,
17361
+ metadata: body.metadata,
17362
+ source: body.source
17363
+ })) {
17364
+ return {
17365
+ error: "Document is outside the allowed RAG access scope",
17366
+ ok: false
17367
+ };
17368
+ }
16890
17369
  const job = createAdminJob("create_document", body.id);
16891
17370
  try {
16892
17371
  const result = await indexManager.createDocument(body);
@@ -16902,7 +17381,7 @@ var ragChat = (config) => {
16902
17381
  throw caught;
16903
17382
  }
16904
17383
  };
16905
- const handleDocumentChunks = async (id) => {
17384
+ const handleDocumentChunks = async (id, request) => {
16906
17385
  if (!indexManager) {
16907
17386
  return {
16908
17387
  error: "RAG chunk preview is not configured",
@@ -16922,6 +17401,18 @@ var ragChat = (config) => {
16922
17401
  ok: false
16923
17402
  };
16924
17403
  }
17404
+ const accessScope = await loadAccessScope(request);
17405
+ if (!matchesAccessScope(accessScope, {
17406
+ documentId: preview.document.id,
17407
+ corpusKey: preview.document.corpusKey,
17408
+ metadata: preview.document.metadata,
17409
+ source: preview.document.source
17410
+ })) {
17411
+ return {
17412
+ error: "document not found",
17413
+ ok: false
17414
+ };
17415
+ }
16925
17416
  const chunks = preview.chunks.map((chunk) => ({
16926
17417
  ...chunk,
16927
17418
  labels: buildRAGSourceLabels({
@@ -16952,7 +17443,7 @@ var ragChat = (config) => {
16952
17443
  })
16953
17444
  };
16954
17445
  };
16955
- const handleDeleteDocument = async (id) => {
17446
+ const handleDeleteDocument = async (id, request) => {
16956
17447
  if (!indexManager?.deleteDocument) {
16957
17448
  return {
16958
17449
  error: "RAG document deletion is not configured",
@@ -16965,6 +17456,13 @@ var ragChat = (config) => {
16965
17456
  ok: false
16966
17457
  };
16967
17458
  }
17459
+ const accessScope = await loadAccessScope(request);
17460
+ if (accessScope && !matchesAccessScope(accessScope, { documentId: id })) {
17461
+ return {
17462
+ error: "Document is outside the allowed RAG access scope",
17463
+ ok: false
17464
+ };
17465
+ }
16968
17466
  const job = createAdminJob("delete_document", id);
16969
17467
  const deleted = await indexManager.deleteDocument(id);
16970
17468
  if (!deleted) {
@@ -16984,7 +17482,7 @@ var ragChat = (config) => {
16984
17482
  ok: true
16985
17483
  };
16986
17484
  };
16987
- const handleReindexDocument = async (id) => {
17485
+ const handleReindexDocument = async (id, request) => {
16988
17486
  if (!indexManager?.reindexDocument) {
16989
17487
  return {
16990
17488
  error: "RAG document reindex is not configured",
@@ -16997,6 +17495,13 @@ var ragChat = (config) => {
16997
17495
  ok: false
16998
17496
  };
16999
17497
  }
17498
+ const accessScope = await loadAccessScope(request);
17499
+ if (accessScope && !matchesAccessScope(accessScope, { documentId: id })) {
17500
+ return {
17501
+ error: "Document is outside the allowed RAG access scope",
17502
+ ok: false
17503
+ };
17504
+ }
17000
17505
  const job = createAdminJob("reindex_document", id);
17001
17506
  try {
17002
17507
  const result = {
@@ -17016,7 +17521,7 @@ var ragChat = (config) => {
17016
17521
  throw caught;
17017
17522
  }
17018
17523
  };
17019
- const handleReindexSource = async (source) => {
17524
+ const handleReindexSource = async (source, request) => {
17020
17525
  if (!indexManager?.reindexSource) {
17021
17526
  return {
17022
17527
  error: "RAG source reindex is not configured",
@@ -17029,6 +17534,15 @@ var ragChat = (config) => {
17029
17534
  ok: false
17030
17535
  };
17031
17536
  }
17537
+ const accessScope = await loadAccessScope(request);
17538
+ if (!matchesAccessScope(accessScope, {
17539
+ source
17540
+ })) {
17541
+ return {
17542
+ error: "Source is outside the allowed RAG access scope",
17543
+ ok: false
17544
+ };
17545
+ }
17032
17546
  const job = createAdminJob("reindex_source", source);
17033
17547
  try {
17034
17548
  const result = {
@@ -17112,25 +17626,33 @@ var ragChat = (config) => {
17112
17626
  ...normalized
17113
17627
  };
17114
17628
  };
17115
- const handleSyncSources = async () => {
17629
+ const handleSyncSources = async (request) => {
17116
17630
  if (!indexManager?.listSyncSources) {
17117
17631
  return {
17118
17632
  error: "RAG source sync is not configured",
17119
17633
  ok: false
17120
17634
  };
17121
17635
  }
17636
+ const accessScope = await loadAccessScope(request);
17122
17637
  return {
17123
17638
  ok: true,
17124
- sources: await indexManager.listSyncSources()
17639
+ sources: await buildSyncSources(accessScope)
17125
17640
  };
17126
17641
  };
17127
- const handleSyncAllSources = async (options) => {
17642
+ const handleSyncAllSources = async (request, options) => {
17128
17643
  if (!indexManager?.syncAllSources) {
17129
17644
  return {
17130
17645
  error: "RAG source sync is not configured",
17131
17646
  ok: false
17132
17647
  };
17133
17648
  }
17649
+ const accessScope = await loadAccessScope(request);
17650
+ if (accessScope?.allowedSyncSourceIds?.length) {
17651
+ return {
17652
+ error: "Scoped sync-all is not allowed; sync individual sources instead",
17653
+ ok: false
17654
+ };
17655
+ }
17134
17656
  const job = createAdminJob("sync_all_sources", undefined, syncJobs);
17135
17657
  const action = createAdminAction("sync_all_sources");
17136
17658
  try {
@@ -17156,7 +17678,7 @@ var ragChat = (config) => {
17156
17678
  completeAdminAction(action);
17157
17679
  return {
17158
17680
  ok: true,
17159
- sources: await buildSyncSources()
17681
+ sources: await buildSyncSources(accessScope)
17160
17682
  };
17161
17683
  } catch (caught) {
17162
17684
  const message = caught instanceof Error ? caught.message : String(caught);
@@ -17165,7 +17687,7 @@ var ragChat = (config) => {
17165
17687
  throw caught;
17166
17688
  }
17167
17689
  };
17168
- const handleSyncSource = async (id, options) => {
17690
+ const handleSyncSource = async (id, request, options) => {
17169
17691
  if (!indexManager?.syncSource) {
17170
17692
  return {
17171
17693
  error: "RAG source sync is not configured",
@@ -17178,6 +17700,13 @@ var ragChat = (config) => {
17178
17700
  ok: false
17179
17701
  };
17180
17702
  }
17703
+ const accessScope = await loadAccessScope(request);
17704
+ if (!matchesSyncSourceScope(accessScope, { id })) {
17705
+ return {
17706
+ error: "Sync source is outside the allowed RAG access scope",
17707
+ ok: false
17708
+ };
17709
+ }
17181
17710
  const job = createAdminJob("sync_source", id, syncJobs);
17182
17711
  const action = createAdminAction("sync_source", undefined, id);
17183
17712
  try {
@@ -17194,7 +17723,7 @@ var ragChat = (config) => {
17194
17723
  }
17195
17724
  completeAdminJob(job);
17196
17725
  completeAdminAction(action);
17197
- const source = (await buildSyncSources()).find((record) => record.id === id);
17726
+ const source = (await buildSyncSources(accessScope)).find((record) => record.id === id);
17198
17727
  return source ? { ok: true, source } : {
17199
17728
  error: "sync source not found",
17200
17729
  ok: false
@@ -17260,7 +17789,7 @@ var ragChat = (config) => {
17260
17789
  };
17261
17790
  return;
17262
17791
  }
17263
- const lastMessage = conversation.messages.findLast((msg) => msg.id === messageId && msg.role === "user");
17792
+ const lastMessage = conversation.messages.findLast((message) => message.id === messageId && message.role === "user");
17264
17793
  if (!lastMessage) {
17265
17794
  yield {
17266
17795
  data: renderers.error("Message not found"),
@@ -17304,7 +17833,7 @@ var ragChat = (config) => {
17304
17833
  const controller = new AbortController;
17305
17834
  abortControllers.set(conversationId, controller);
17306
17835
  const history = buildHistory(conversation);
17307
- const lastMessageIndex = conversation.messages.findIndex((msg) => msg.id === messageId);
17836
+ const lastMessageIndex = conversation.messages.findIndex((message) => message.id === messageId);
17308
17837
  const userHistory = lastMessageIndex >= 0 ? history.slice(0, lastMessageIndex) : history;
17309
17838
  const messageWithContext = buildUserMessage2(content, lastMessage.attachments, ragContext);
17310
17839
  const sseStream = streamAIToSSE(conversationId, assistantMessageId, {
@@ -17330,24 +17859,24 @@ var ragChat = (config) => {
17330
17859
  };
17331
17860
  return new Elysia2().ws(path, {
17332
17861
  message: async (ws, raw) => {
17333
- const msg = parseAIMessage(raw);
17334
- if (!msg) {
17862
+ const message = parseAIMessage(raw);
17863
+ if (!message) {
17335
17864
  return;
17336
17865
  }
17337
- if (msg.type === "cancel") {
17338
- handleCancel(msg.conversationId);
17866
+ if (message.type === "cancel") {
17867
+ handleCancel(message.conversationId);
17339
17868
  return;
17340
17869
  }
17341
- if (msg.type === "branch") {
17342
- await handleBranch(ws, msg.messageId, msg.conversationId);
17870
+ if (message.type === "branch") {
17871
+ await handleBranch(ws, message.messageId, message.conversationId);
17343
17872
  return;
17344
17873
  }
17345
- if (msg.type === "message") {
17346
- await handleMessage(ws, msg.content, msg.conversationId, msg.attachments);
17874
+ if (message.type === "message") {
17875
+ await handleMessage(ws, message.content, message.conversationId, message.attachments);
17347
17876
  }
17348
17877
  }
17349
17878
  }).post(`${path}/search`, async ({ body, request, set }) => {
17350
- const result = await handleSearch(body);
17879
+ const result = await handleSearch(body, request);
17351
17880
  if (!result.ok) {
17352
17881
  set.status = result.error === "Invalid payload" || result.error?.startsWith("Expected payload shape:") ? HTTP_STATUS_BAD_REQUEST : HTTP_STATUS_NOT_FOUND;
17353
17882
  }
@@ -17424,6 +17953,16 @@ var ragChat = (config) => {
17424
17953
  }
17425
17954
  return result;
17426
17955
  }).post(`${path}/traces/prune`, async ({ body, request, set }) => {
17956
+ const denied = await authorizeMutationRoute(request, "prune_search_traces", {
17957
+ fallback: "Search trace pruning is not allowed"
17958
+ });
17959
+ if (denied) {
17960
+ set.status = 403;
17961
+ if (config.htmx && isHTMXRequest(request)) {
17962
+ return toHTMXResponse(workflowRenderers.error(denied.error ?? "Search trace prune failed"), getNumericStatus(set.status));
17963
+ }
17964
+ return denied;
17965
+ }
17427
17966
  const result = await handleTracePrune(body);
17428
17967
  if (!result.ok) {
17429
17968
  set.status = HTTP_STATUS_BAD_REQUEST;
@@ -17454,7 +17993,7 @@ var ragChat = (config) => {
17454
17993
  }
17455
17994
  return result;
17456
17995
  }).post(`${path}/compare/retrieval`, async ({ body, request, set }) => {
17457
- const result = await handleEvaluateRetrievals(body);
17996
+ const result = await handleEvaluateRetrievals(body, request);
17458
17997
  if (!result.ok) {
17459
17998
  set.status = HTTP_STATUS_BAD_REQUEST;
17460
17999
  }
@@ -17469,7 +18008,7 @@ var ragChat = (config) => {
17469
18008
  }
17470
18009
  return result;
17471
18010
  }).get(`${path}/compare/retrieval/history`, async ({ query, request, set }) => {
17472
- const result = await handleRetrievalComparisonHistory(query);
18011
+ const result = await handleRetrievalComparisonHistory(query, request);
17473
18012
  if (!result.ok) {
17474
18013
  set.status = HTTP_STATUS_BAD_REQUEST;
17475
18014
  }
@@ -17484,7 +18023,7 @@ var ragChat = (config) => {
17484
18023
  }
17485
18024
  return result;
17486
18025
  }).get(`${path}/compare/retrieval/baselines`, async ({ query, request, set }) => {
17487
- const result = await handleRetrievalBaselineList(query);
18026
+ const result = await handleRetrievalBaselineList(query, request);
17488
18027
  if (!result.ok) {
17489
18028
  set.status = HTTP_STATUS_BAD_REQUEST;
17490
18029
  }
@@ -17499,7 +18038,7 @@ var ragChat = (config) => {
17499
18038
  }
17500
18039
  return result;
17501
18040
  }).get(`${path}/compare/retrieval/baselines/decisions`, async ({ query, request, set }) => {
17502
- const result = await handleRetrievalReleaseDecisionList(query);
18041
+ const result = await handleRetrievalReleaseDecisionList(query, request);
17503
18042
  if (!result.ok) {
17504
18043
  set.status = HTTP_STATUS_BAD_REQUEST;
17505
18044
  }
@@ -17604,6 +18143,16 @@ var ragChat = (config) => {
17604
18143
  }
17605
18144
  return result;
17606
18145
  }).post(`${path}/compare/retrieval/handoffs/incidents/acknowledge`, async ({ body, request, set }) => {
18146
+ const denied = await authorizeMutationRoute(request, "manage_retrieval_admin", {
18147
+ fallback: "Retrieval lane handoff incident acknowledgement is not allowed"
18148
+ });
18149
+ if (denied) {
18150
+ set.status = 403;
18151
+ if (config.htmx && isHTMXRequest(request)) {
18152
+ return toHTMXResponse(workflowRenderers.error(denied.error ?? "Retrieval lane handoff incident acknowledgement failed"), getNumericStatus(set.status));
18153
+ }
18154
+ return denied;
18155
+ }
17607
18156
  const result = await handleRetrievalLaneHandoffIncidentAcknowledge(body);
17608
18157
  if (!result.ok) {
17609
18158
  set.status = HTTP_STATUS_BAD_REQUEST;
@@ -17619,6 +18168,16 @@ var ragChat = (config) => {
17619
18168
  }
17620
18169
  return result;
17621
18170
  }).post(`${path}/compare/retrieval/handoffs/incidents/unacknowledge`, async ({ body, request, set }) => {
18171
+ const denied = await authorizeMutationRoute(request, "manage_retrieval_admin", {
18172
+ fallback: "Retrieval lane handoff incident unacknowledge is not allowed"
18173
+ });
18174
+ if (denied) {
18175
+ set.status = 403;
18176
+ if (config.htmx && isHTMXRequest(request)) {
18177
+ return toHTMXResponse(workflowRenderers.error(denied.error ?? "Retrieval lane handoff incident unacknowledge failed"), getNumericStatus(set.status));
18178
+ }
18179
+ return denied;
18180
+ }
17622
18181
  const result = await handleRetrievalLaneHandoffIncidentUnacknowledge(body);
17623
18182
  if (!result.ok) {
17624
18183
  set.status = HTTP_STATUS_BAD_REQUEST;
@@ -17634,6 +18193,16 @@ var ragChat = (config) => {
17634
18193
  }
17635
18194
  return result;
17636
18195
  }).post(`${path}/compare/retrieval/handoffs/incidents/resolve`, async ({ body, request, set }) => {
18196
+ const denied = await authorizeMutationRoute(request, "manage_retrieval_admin", {
18197
+ fallback: "Retrieval lane handoff incident resolve is not allowed"
18198
+ });
18199
+ if (denied) {
18200
+ set.status = 403;
18201
+ if (config.htmx && isHTMXRequest(request)) {
18202
+ return toHTMXResponse(workflowRenderers.error(denied.error ?? "Retrieval lane handoff incident resolve failed"), getNumericStatus(set.status));
18203
+ }
18204
+ return denied;
18205
+ }
17637
18206
  const result = await handleResolveRetrievalLaneHandoffIncident(body);
17638
18207
  if (!result.ok) {
17639
18208
  set.status = HTTP_STATUS_BAD_REQUEST;
@@ -17649,6 +18218,16 @@ var ragChat = (config) => {
17649
18218
  }
17650
18219
  return result;
17651
18220
  }).post(`${path}/compare/retrieval/handoffs/decide`, async ({ body, request, set }) => {
18221
+ const denied = await authorizeMutationRoute(request, "manage_retrieval_admin", {
18222
+ fallback: "Retrieval lane handoff decision is not allowed"
18223
+ });
18224
+ if (denied) {
18225
+ set.status = 403;
18226
+ if (config.htmx && isHTMXRequest(request)) {
18227
+ return toHTMXResponse(workflowRenderers.error(denied.error ?? "Retrieval lane handoff decision failed"), getNumericStatus(set.status));
18228
+ }
18229
+ return denied;
18230
+ }
17652
18231
  const result = await handleRetrievalLaneHandoffDecision(body);
17653
18232
  if (!result.ok) {
17654
18233
  set.status = HTTP_STATUS_BAD_REQUEST;
@@ -17709,6 +18288,16 @@ var ragChat = (config) => {
17709
18288
  }
17710
18289
  return result;
17711
18290
  }).post(`${path}/compare/retrieval/incidents/remediations`, async ({ body, request, set }) => {
18291
+ const denied = await authorizeMutationRoute(request, "manage_retrieval_admin", {
18292
+ fallback: "Retrieval incident remediation decision record is not allowed"
18293
+ });
18294
+ if (denied) {
18295
+ set.status = 403;
18296
+ if (config.htmx && isHTMXRequest(request)) {
18297
+ return toHTMXResponse(workflowRenderers.error(denied.error ?? "Retrieval incident remediation decision record failed"), getNumericStatus(set.status));
18298
+ }
18299
+ return denied;
18300
+ }
17712
18301
  const result = await handleRecordRetrievalIncidentRemediationDecision(body);
17713
18302
  if (!result.ok) {
17714
18303
  set.status = HTTP_STATUS_BAD_REQUEST;
@@ -17724,6 +18313,16 @@ var ragChat = (config) => {
17724
18313
  }
17725
18314
  return result;
17726
18315
  }).post(`${path}/compare/retrieval/incidents/remediations/execute`, async ({ body, request, set }) => {
18316
+ const denied = await authorizeMutationRoute(request, "manage_retrieval_admin", {
18317
+ fallback: "Retrieval incident remediation execution is not allowed"
18318
+ });
18319
+ if (denied) {
18320
+ set.status = 403;
18321
+ if (config.htmx && isHTMXRequest(request)) {
18322
+ return toHTMXResponse(workflowRenderers.error(denied.error ?? "Retrieval incident remediation execution failed"), getNumericStatus(set.status));
18323
+ }
18324
+ return denied;
18325
+ }
17727
18326
  const result = await handleExecuteRetrievalIncidentRemediation(body);
17728
18327
  if (!result.ok) {
17729
18328
  set.status = HTTP_STATUS_BAD_REQUEST;
@@ -17739,6 +18338,16 @@ var ragChat = (config) => {
17739
18338
  }
17740
18339
  return result;
17741
18340
  }).post(`${path}/compare/retrieval/incidents/remediations/execute/bulk`, async ({ body, request, set }) => {
18341
+ const denied = await authorizeMutationRoute(request, "manage_retrieval_admin", {
18342
+ fallback: "Bulk retrieval incident remediation execution is not allowed"
18343
+ });
18344
+ if (denied) {
18345
+ set.status = 403;
18346
+ if (config.htmx && isHTMXRequest(request)) {
18347
+ return toHTMXResponse(workflowRenderers.error(denied.error ?? "Bulk retrieval incident remediation execution failed"), getNumericStatus(set.status));
18348
+ }
18349
+ return denied;
18350
+ }
17742
18351
  const result = await handleBulkExecuteRetrievalIncidentRemediations(body);
17743
18352
  if (!result.ok) {
17744
18353
  set.status = HTTP_STATUS_BAD_REQUEST;
@@ -17754,6 +18363,16 @@ var ragChat = (config) => {
17754
18363
  }
17755
18364
  return result;
17756
18365
  }).post(`${path}/compare/retrieval/incidents/acknowledge`, async ({ body, request, set }) => {
18366
+ const denied = await authorizeMutationRoute(request, "manage_retrieval_admin", {
18367
+ fallback: "Retrieval release incident acknowledgement is not allowed"
18368
+ });
18369
+ if (denied) {
18370
+ set.status = 403;
18371
+ if (config.htmx && isHTMXRequest(request)) {
18372
+ return toHTMXResponse(workflowRenderers.error(denied.error ?? "Retrieval release incident acknowledgement failed"), getNumericStatus(set.status));
18373
+ }
18374
+ return denied;
18375
+ }
17757
18376
  const result = await handleAcknowledgeRetrievalReleaseIncident(body);
17758
18377
  if (!result.ok) {
17759
18378
  set.status = HTTP_STATUS_BAD_REQUEST;
@@ -17769,6 +18388,16 @@ var ragChat = (config) => {
17769
18388
  }
17770
18389
  return result;
17771
18390
  }).post(`${path}/compare/retrieval/incidents/unacknowledge`, async ({ body, request, set }) => {
18391
+ const denied = await authorizeMutationRoute(request, "manage_retrieval_admin", {
18392
+ fallback: "Retrieval release incident unacknowledge is not allowed"
18393
+ });
18394
+ if (denied) {
18395
+ set.status = 403;
18396
+ if (config.htmx && isHTMXRequest(request)) {
18397
+ return toHTMXResponse(workflowRenderers.error(denied.error ?? "Retrieval release incident unacknowledge failed"), getNumericStatus(set.status));
18398
+ }
18399
+ return denied;
18400
+ }
17772
18401
  const result = await handleUnacknowledgeRetrievalReleaseIncident(body);
17773
18402
  if (!result.ok) {
17774
18403
  set.status = HTTP_STATUS_BAD_REQUEST;
@@ -17784,6 +18413,16 @@ var ragChat = (config) => {
17784
18413
  }
17785
18414
  return result;
17786
18415
  }).post(`${path}/compare/retrieval/incidents/resolve`, async ({ body, request, set }) => {
18416
+ const denied = await authorizeMutationRoute(request, "manage_retrieval_admin", {
18417
+ fallback: "Retrieval release incident resolve is not allowed"
18418
+ });
18419
+ if (denied) {
18420
+ set.status = 403;
18421
+ if (config.htmx && isHTMXRequest(request)) {
18422
+ return toHTMXResponse(workflowRenderers.error(denied.error ?? "Retrieval release incident resolve failed"), getNumericStatus(set.status));
18423
+ }
18424
+ return denied;
18425
+ }
17787
18426
  const result = await handleResolveRetrievalReleaseIncident(body);
17788
18427
  if (!result.ok) {
17789
18428
  set.status = HTTP_STATUS_BAD_REQUEST;
@@ -17814,7 +18453,15 @@ var ragChat = (config) => {
17814
18453
  }
17815
18454
  return result;
17816
18455
  }).post(`${path}/compare/retrieval/baselines/approve`, async ({ body, request, set }) => {
17817
- const result = await handleRetrievalReleaseDecisionAction(body, "approve");
18456
+ const denied = await authorizeMutationRoute(request, "manage_retrieval_admin", { fallback: "Retrieval approval is not allowed" });
18457
+ if (denied) {
18458
+ set.status = 403;
18459
+ if (config.htmx && isHTMXRequest(request)) {
18460
+ return toHTMXResponse(workflowRenderers.error(denied.error ?? "Retrieval approval failed"), getNumericStatus(set.status));
18461
+ }
18462
+ return denied;
18463
+ }
18464
+ const result = await handleRetrievalReleaseDecisionAction(body, "approve", request);
17818
18465
  if (!result.ok) {
17819
18466
  set.status = HTTP_STATUS_BAD_REQUEST;
17820
18467
  }
@@ -17829,7 +18476,15 @@ var ragChat = (config) => {
17829
18476
  }
17830
18477
  return result;
17831
18478
  }).post(`${path}/compare/retrieval/baselines/reject`, async ({ body, request, set }) => {
17832
- const result = await handleRetrievalReleaseDecisionAction(body, "reject");
18479
+ const denied = await authorizeMutationRoute(request, "manage_retrieval_admin", { fallback: "Retrieval rejection is not allowed" });
18480
+ if (denied) {
18481
+ set.status = 403;
18482
+ if (config.htmx && isHTMXRequest(request)) {
18483
+ return toHTMXResponse(workflowRenderers.error(denied.error ?? "Retrieval rejection failed"), getNumericStatus(set.status));
18484
+ }
18485
+ return denied;
18486
+ }
18487
+ const result = await handleRetrievalReleaseDecisionAction(body, "reject", request);
17833
18488
  if (!result.ok) {
17834
18489
  set.status = HTTP_STATUS_BAD_REQUEST;
17835
18490
  }
@@ -17844,7 +18499,15 @@ var ragChat = (config) => {
17844
18499
  }
17845
18500
  return result;
17846
18501
  }).post(`${path}/compare/retrieval/baselines/promote`, async ({ body, request, set }) => {
17847
- const result = await handlePromoteRetrievalBaseline(body);
18502
+ const denied = await authorizeMutationRoute(request, "manage_retrieval_admin", { fallback: "Retrieval baseline promotion is not allowed" });
18503
+ if (denied) {
18504
+ set.status = 403;
18505
+ if (config.htmx && isHTMXRequest(request)) {
18506
+ return toHTMXResponse(workflowRenderers.error(denied.error ?? "Retrieval baseline promotion failed"), getNumericStatus(set.status));
18507
+ }
18508
+ return denied;
18509
+ }
18510
+ const result = await handlePromoteRetrievalBaseline(body, request);
17848
18511
  if (!result.ok) {
17849
18512
  set.status = HTTP_STATUS_BAD_REQUEST;
17850
18513
  }
@@ -17859,6 +18522,16 @@ var ragChat = (config) => {
17859
18522
  }
17860
18523
  return result;
17861
18524
  }).post(`${path}/compare/retrieval/baselines/promote-lane`, async ({ body, request, set }) => {
18525
+ const denied = await authorizeMutationRoute(request, "manage_retrieval_admin", {
18526
+ fallback: "Retrieval rollout-lane promotion is not allowed"
18527
+ });
18528
+ if (denied) {
18529
+ set.status = 403;
18530
+ if (config.htmx && isHTMXRequest(request)) {
18531
+ return toHTMXResponse(workflowRenderers.error(denied.error ?? "Retrieval rollout-lane promotion failed"), getNumericStatus(set.status));
18532
+ }
18533
+ return denied;
18534
+ }
17862
18535
  const result = await handlePromoteRetrievalBaselineToLane(body);
17863
18536
  if (!result.ok) {
17864
18537
  set.status = HTTP_STATUS_BAD_REQUEST;
@@ -17874,7 +18547,17 @@ var ragChat = (config) => {
17874
18547
  }
17875
18548
  return result;
17876
18549
  }).post(`${path}/compare/retrieval/baselines/promote-run`, async ({ body, request, set }) => {
17877
- const result = await handlePromoteRetrievalBaselineFromRun(body);
18550
+ const denied = await authorizeMutationRoute(request, "manage_retrieval_admin", {
18551
+ fallback: "Retrieval baseline promotion from run is not allowed"
18552
+ });
18553
+ if (denied) {
18554
+ set.status = 403;
18555
+ if (config.htmx && isHTMXRequest(request)) {
18556
+ return toHTMXResponse(workflowRenderers.error(denied.error ?? "Retrieval baseline promotion from run failed"), getNumericStatus(set.status));
18557
+ }
18558
+ return denied;
18559
+ }
18560
+ const result = await handlePromoteRetrievalBaselineFromRun(body, request);
17878
18561
  if (!result.ok) {
17879
18562
  set.status = HTTP_STATUS_BAD_REQUEST;
17880
18563
  }
@@ -17889,7 +18572,15 @@ var ragChat = (config) => {
17889
18572
  }
17890
18573
  return result;
17891
18574
  }).post(`${path}/compare/retrieval/baselines/revert`, async ({ body, request, set }) => {
17892
- const result = await handleRevertRetrievalBaseline(body);
18575
+ const denied = await authorizeMutationRoute(request, "manage_retrieval_admin", { fallback: "Retrieval baseline revert is not allowed" });
18576
+ if (denied) {
18577
+ set.status = 403;
18578
+ if (config.htmx && isHTMXRequest(request)) {
18579
+ return toHTMXResponse(workflowRenderers.error(denied.error ?? "Retrieval baseline revert failed"), getNumericStatus(set.status));
18580
+ }
18581
+ return denied;
18582
+ }
18583
+ const result = await handleRevertRetrievalBaseline(body, request);
17893
18584
  if (!result.ok) {
17894
18585
  set.status = HTTP_STATUS_BAD_REQUEST;
17895
18586
  }
@@ -17904,7 +18595,7 @@ var ragChat = (config) => {
17904
18595
  }
17905
18596
  return result;
17906
18597
  }).post(`${path}/evaluate`, async ({ body, request, set }) => {
17907
- const result = await handleEvaluate(body);
18598
+ const result = await handleEvaluate(body, request);
17908
18599
  if (!result.ok) {
17909
18600
  set.status = HTTP_STATUS_BAD_REQUEST;
17910
18601
  }
@@ -17919,7 +18610,7 @@ var ragChat = (config) => {
17919
18610
  }
17920
18611
  return result;
17921
18612
  }).get(`${path}/status`, async ({ request }) => {
17922
- const result = await handleStatus();
18613
+ const result = await handleStatus(request);
17923
18614
  if (config.htmx && isHTMXRequest(request)) {
17924
18615
  return toHTMXResponse(workflowRenderers.status({
17925
18616
  capabilities: result.capabilities,
@@ -17988,20 +18679,21 @@ var ragChat = (config) => {
17988
18679
  });
17989
18680
  }
17990
18681
  return result;
17991
- }).get(`${path}/status/release`, async () => {
17992
- return handleRetrievalReleaseStatus();
18682
+ }).get(`${path}/status/release`, async ({ request }) => {
18683
+ return handleRetrievalReleaseStatus(request);
17993
18684
  }).get(`${path}/status/release/incidents`, async () => {
17994
18685
  return handleRetrievalReleaseIncidentStatus();
17995
18686
  }).get(`${path}/status/release/remediations`, async () => {
17996
18687
  return handleRetrievalIncidentRemediationStatus();
17997
- }).get(`${path}/status/release/drift`, async () => {
17998
- return handleRetrievalReleaseDriftStatus();
17999
- }).get(`${path}/status/handoffs/incidents`, async () => {
18000
- return handleRetrievalLaneHandoffIncidentStatus();
18001
- }).get(`${path}/status/handoffs`, async () => {
18002
- return handleRetrievalLaneHandoffStatus();
18688
+ }).get(`${path}/status/release/drift`, async ({ request }) => {
18689
+ return handleRetrievalReleaseDriftStatus(request);
18690
+ }).get(`${path}/status/handoffs/incidents`, async ({ request }) => {
18691
+ return handleRetrievalLaneHandoffIncidentStatus(request);
18692
+ }).get(`${path}/status/handoffs`, async ({ request }) => {
18693
+ return handleRetrievalLaneHandoffStatus(request);
18003
18694
  }).get(`${path}/ops`, async ({ request }) => {
18004
- const result = await handleOps();
18695
+ await ensureJobStateLoaded();
18696
+ const result = await handleOps(request);
18005
18697
  if (config.htmx && isHTMXRequest(request)) {
18006
18698
  return toHTMXResponse(workflowRenderers.status({
18007
18699
  capabilities: result.capabilities,
@@ -18011,7 +18703,7 @@ var ragChat = (config) => {
18011
18703
  }
18012
18704
  return result;
18013
18705
  }).get(`${path}/documents`, async ({ query, request, set }) => {
18014
- const result = await handleDocuments(getStringProperty(query, "kind"));
18706
+ const result = await handleDocuments(getStringProperty(query, "kind"), request);
18015
18707
  if (!result.ok) {
18016
18708
  set.status = HTTP_STATUS_NOT_FOUND;
18017
18709
  }
@@ -18025,9 +18717,19 @@ var ragChat = (config) => {
18025
18717
  }
18026
18718
  return result;
18027
18719
  }).post(`${path}/documents`, async ({ body, request, set }) => {
18028
- const result = await handleCreateDocument(body);
18720
+ await ensureJobStateLoaded();
18721
+ const authorization = await checkAuthorization(request, "create_document");
18722
+ if (!authorization.allowed) {
18723
+ set.status = 403;
18724
+ const result2 = buildAuthorizationFailure(authorization, "Document creation is not allowed");
18725
+ if (config.htmx && isHTMXRequest(request)) {
18726
+ return toHTMXResponse(workflowRenderers.error(result2.error ?? "Failed to create document"), getNumericStatus(set.status));
18727
+ }
18728
+ return result2;
18729
+ }
18730
+ const result = await handleCreateDocument(body, request);
18029
18731
  if (!result.ok) {
18030
- const status = result.error?.includes("not configured") ? HTTP_STATUS_NOT_FOUND : HTTP_STATUS_BAD_REQUEST;
18732
+ const status = isAccessScopeError(result.error) ? 403 : result.error?.includes("not configured") ? HTTP_STATUS_NOT_FOUND : HTTP_STATUS_BAD_REQUEST;
18031
18733
  set.status = status;
18032
18734
  }
18033
18735
  if (config.htmx && isHTMXRequest(request)) {
@@ -18038,9 +18740,9 @@ var ragChat = (config) => {
18038
18740
  }
18039
18741
  return result;
18040
18742
  }).get(`${path}/documents/:id/chunks`, async ({ params, request, set }) => {
18041
- const result = await handleDocumentChunks(typeof params.id === "string" ? params.id.trim() : "");
18743
+ const result = await handleDocumentChunks(typeof params.id === "string" ? params.id.trim() : "", request);
18042
18744
  if (!result.ok) {
18043
- const status = result.error === "document id is required" ? HTTP_STATUS_BAD_REQUEST : HTTP_STATUS_NOT_FOUND;
18745
+ const status = isAccessScopeError(result.error) ? 403 : result.error === "document id is required" ? HTTP_STATUS_BAD_REQUEST : HTTP_STATUS_NOT_FOUND;
18044
18746
  set.status = status;
18045
18747
  }
18046
18748
  if (config.htmx && isHTMXRequest(request)) {
@@ -18054,10 +18756,13 @@ var ragChat = (config) => {
18054
18756
  const result = await handleBackends();
18055
18757
  if (!result.ok) {
18056
18758
  set.status = HTTP_STATUS_NOT_FOUND;
18759
+ if (isAccessScopeError(result.error)) {
18760
+ set.status = 403;
18761
+ }
18057
18762
  }
18058
18763
  return result;
18059
18764
  }).get(`${path}/sync`, async ({ request, set }) => {
18060
- const result = await handleSyncSources();
18765
+ const result = await handleSyncSources(request);
18061
18766
  if (!result.ok) {
18062
18767
  set.status = HTTP_STATUS_NOT_FOUND;
18063
18768
  }
@@ -18072,8 +18777,18 @@ var ragChat = (config) => {
18072
18777
  }
18073
18778
  return result;
18074
18779
  }).post(`${path}/sync`, async ({ body, request, set }) => {
18780
+ await ensureJobStateLoaded();
18781
+ const authorization = await checkAuthorization(request, "sync_all_sources");
18782
+ if (!authorization.allowed) {
18783
+ set.status = 403;
18784
+ const result2 = buildAuthorizationFailure(authorization, "Source sync is not allowed");
18785
+ if (config.htmx && isHTMXRequest(request)) {
18786
+ return toHTMXResponse(workflowRenderers.error(result2.error ?? "Failed to sync sources"), getNumericStatus(set.status));
18787
+ }
18788
+ return result2;
18789
+ }
18075
18790
  const background = getBooleanProperty(body, "background");
18076
- const result = await handleSyncAllSources({ background });
18791
+ const result = await handleSyncAllSources(request, { background });
18077
18792
  if (!result.ok) {
18078
18793
  set.status = HTTP_STATUS_NOT_FOUND;
18079
18794
  }
@@ -18088,10 +18803,28 @@ var ragChat = (config) => {
18088
18803
  }
18089
18804
  return result;
18090
18805
  }).post(`${path}/sync/:id`, async ({ body, params, request, set }) => {
18806
+ await ensureJobStateLoaded();
18807
+ const syncSourceId = typeof params.id === "string" ? params.id.trim() : "";
18808
+ const authorization = await checkAuthorization(request, "sync_source", {
18809
+ sourceId: syncSourceId
18810
+ });
18811
+ if (!authorization.allowed) {
18812
+ set.status = 403;
18813
+ const result2 = buildAuthorizationFailure(authorization, "Source sync is not allowed");
18814
+ if (config.htmx && isHTMXRequest(request)) {
18815
+ return toHTMXResponse(workflowRenderers.error(result2.error ?? "Failed to sync source"), getNumericStatus(set.status));
18816
+ }
18817
+ return result2;
18818
+ }
18091
18819
  const background = getBooleanProperty(body, "background");
18092
- const result = await handleSyncSource(typeof params.id === "string" ? params.id.trim() : "", { background });
18820
+ const result = await handleSyncSource(syncSourceId, request, {
18821
+ background
18822
+ });
18093
18823
  if (!result.ok) {
18094
18824
  set.status = result.error === "sync source id is required" ? HTTP_STATUS_BAD_REQUEST : HTTP_STATUS_NOT_FOUND;
18825
+ if (isAccessScopeError(result.error)) {
18826
+ set.status = 403;
18827
+ }
18095
18828
  }
18096
18829
  if (config.htmx && isHTMXRequest(request)) {
18097
18830
  const html = result.ok ? workflowRenderers.mutationResult({
@@ -18104,6 +18837,18 @@ var ragChat = (config) => {
18104
18837
  }
18105
18838
  return result;
18106
18839
  }).post(`${path}/ingest`, async ({ body, request, set }) => {
18840
+ await ensureJobStateLoaded();
18841
+ const authorization = await checkAuthorization(request, "ingest", {
18842
+ path: `${path}/ingest`
18843
+ });
18844
+ if (!authorization.allowed) {
18845
+ set.status = 403;
18846
+ const result2 = buildAuthorizationFailure(authorization, "RAG ingest is not allowed");
18847
+ if (config.htmx && isHTMXRequest(request)) {
18848
+ return toHTMXResponse(workflowRenderers.error(result2.error ?? "RAG ingest failed"), getNumericStatus(set.status));
18849
+ }
18850
+ return result2;
18851
+ }
18107
18852
  const result = await handleIngest(body);
18108
18853
  if (!result.ok) {
18109
18854
  set.status = HTTP_STATUS_BAD_REQUEST;
@@ -18115,7 +18860,13 @@ var ragChat = (config) => {
18115
18860
  return toHTMXResponse(workflowRenderers.mutationResult(result), HTTP_STATUS_OK, { "HX-Trigger": "rag:mutated" });
18116
18861
  }
18117
18862
  return result;
18118
- }).delete(`${path}/index`, async () => {
18863
+ }).delete(`${path}/index`, async ({ request, set }) => {
18864
+ await ensureJobStateLoaded();
18865
+ const authorization = await checkAuthorization(request, "clear_index");
18866
+ if (!authorization.allowed) {
18867
+ set.status = 403;
18868
+ return buildAuthorizationFailure(authorization, "Index clearing is not allowed");
18869
+ }
18119
18870
  if (!ragStore) {
18120
18871
  return { ok: false };
18121
18872
  }
@@ -18134,9 +18885,22 @@ var ragChat = (config) => {
18134
18885
  }
18135
18886
  return { ok: true };
18136
18887
  }).delete(`${path}/documents/:id`, async ({ params, request, set }) => {
18137
- const result = await handleDeleteDocument(typeof params.id === "string" ? params.id.trim() : "");
18888
+ await ensureJobStateLoaded();
18889
+ const documentId = typeof params.id === "string" ? params.id.trim() : "";
18890
+ const authorization = await checkAuthorization(request, "delete_document", {
18891
+ documentId
18892
+ });
18893
+ if (!authorization.allowed) {
18894
+ set.status = 403;
18895
+ const result2 = buildAuthorizationFailure(authorization, "Document deletion is not allowed");
18896
+ if (config.htmx && isHTMXRequest(request)) {
18897
+ return toHTMXResponse(workflowRenderers.error(result2.error ?? "Failed to delete document"), getNumericStatus(set.status), { "HX-Trigger": "rag:mutated" });
18898
+ }
18899
+ return result2;
18900
+ }
18901
+ const result = await handleDeleteDocument(documentId, request);
18138
18902
  if (!result.ok) {
18139
- const status = result.error === "document id is required" ? HTTP_STATUS_BAD_REQUEST : HTTP_STATUS_NOT_FOUND;
18903
+ const status = isAccessScopeError(result.error) ? 403 : result.error === "document id is required" ? HTTP_STATUS_BAD_REQUEST : HTTP_STATUS_NOT_FOUND;
18140
18904
  set.status = status;
18141
18905
  }
18142
18906
  if (config.htmx && isHTMXRequest(request)) {
@@ -18147,9 +18911,20 @@ var ragChat = (config) => {
18147
18911
  }
18148
18912
  return result;
18149
18913
  }).post(`${path}/reindex/documents/:id`, async ({ params, request, set }) => {
18150
- const result = await handleReindexDocument(typeof params.id === "string" ? params.id.trim() : "");
18914
+ await ensureJobStateLoaded();
18915
+ const documentId = typeof params.id === "string" ? params.id.trim() : "";
18916
+ const authorization = await checkAuthorization(request, "reindex_document", { documentId });
18917
+ if (!authorization.allowed) {
18918
+ set.status = 403;
18919
+ const result2 = buildAuthorizationFailure(authorization, "Document reindex is not allowed");
18920
+ if (config.htmx && isHTMXRequest(request)) {
18921
+ return toHTMXResponse(workflowRenderers.error(result2.error ?? "Failed to reindex document"), getNumericStatus(set.status), { "HX-Trigger": "rag:mutated" });
18922
+ }
18923
+ return result2;
18924
+ }
18925
+ const result = await handleReindexDocument(documentId, request);
18151
18926
  if (!result.ok) {
18152
- set.status = result.error === "document id is required" ? HTTP_STATUS_BAD_REQUEST : HTTP_STATUS_NOT_FOUND;
18927
+ set.status = isAccessScopeError(result.error) ? 403 : result.error === "document id is required" ? HTTP_STATUS_BAD_REQUEST : HTTP_STATUS_NOT_FOUND;
18153
18928
  }
18154
18929
  if (config.htmx && isHTMXRequest(request)) {
18155
18930
  const html = result.ok ? workflowRenderers.mutationResult(result) : workflowRenderers.error(result.error ?? "Failed to reindex document");
@@ -18159,10 +18934,22 @@ var ragChat = (config) => {
18159
18934
  }
18160
18935
  return result;
18161
18936
  }).post(`${path}/reindex/source`, async ({ body, request, set }) => {
18937
+ await ensureJobStateLoaded();
18162
18938
  const source = getStringProperty(body, "source")?.trim() ?? "";
18163
- const result = await handleReindexSource(source);
18939
+ const authorization = await checkAuthorization(request, "reindex_source", {
18940
+ source
18941
+ });
18942
+ if (!authorization.allowed) {
18943
+ set.status = 403;
18944
+ const result2 = buildAuthorizationFailure(authorization, "Source reindex is not allowed");
18945
+ if (config.htmx && isHTMXRequest(request)) {
18946
+ return toHTMXResponse(workflowRenderers.error(result2.error ?? "Failed to reindex source"), getNumericStatus(set.status), { "HX-Trigger": "rag:mutated" });
18947
+ }
18948
+ return result2;
18949
+ }
18950
+ const result = await handleReindexSource(source, request);
18164
18951
  if (!result.ok) {
18165
- set.status = result.error === "source is required" ? HTTP_STATUS_BAD_REQUEST : HTTP_STATUS_NOT_FOUND;
18952
+ set.status = isAccessScopeError(result.error) ? 403 : result.error === "source is required" ? HTTP_STATUS_BAD_REQUEST : HTTP_STATUS_NOT_FOUND;
18166
18953
  }
18167
18954
  if (config.htmx && isHTMXRequest(request)) {
18168
18955
  const html = result.ok ? workflowRenderers.mutationResult(result) : workflowRenderers.error(result.error ?? "Failed to reindex source");
@@ -18172,6 +18959,16 @@ var ragChat = (config) => {
18172
18959
  }
18173
18960
  return result;
18174
18961
  }).post(`${path}/reseed`, async ({ request, set }) => {
18962
+ await ensureJobStateLoaded();
18963
+ const authorization = await checkAuthorization(request, "reseed");
18964
+ if (!authorization.allowed) {
18965
+ set.status = 403;
18966
+ const result2 = buildAuthorizationFailure(authorization, "Index reseed is not allowed");
18967
+ if (config.htmx && isHTMXRequest(request)) {
18968
+ return toHTMXResponse(workflowRenderers.error(result2.error ?? "Failed to reseed index"), getNumericStatus(set.status), { "HX-Trigger": "rag:mutated" });
18969
+ }
18970
+ return result2;
18971
+ }
18175
18972
  const result = await handleReseed();
18176
18973
  if (!result.ok) {
18177
18974
  set.status = 404;
@@ -18184,6 +18981,16 @@ var ragChat = (config) => {
18184
18981
  }
18185
18982
  return result;
18186
18983
  }).post(`${path}/reset`, async ({ request, set }) => {
18984
+ await ensureJobStateLoaded();
18985
+ const authorization = await checkAuthorization(request, "reset");
18986
+ if (!authorization.allowed) {
18987
+ set.status = 403;
18988
+ const result2 = buildAuthorizationFailure(authorization, "Index reset is not allowed");
18989
+ if (config.htmx && isHTMXRequest(request)) {
18990
+ return toHTMXResponse(workflowRenderers.error(result2.error ?? "Failed to reset index"), getNumericStatus(set.status), { "HX-Trigger": "rag:mutated" });
18991
+ }
18992
+ return result2;
18993
+ }
18187
18994
  const result = await handleReset();
18188
18995
  if (!result.ok) {
18189
18996
  set.status = 404;
@@ -18271,6 +19078,31 @@ var createHeuristicRAGRetrievalStrategy = (options = {}) => ({
18271
19078
  return;
18272
19079
  }
18273
19080
  });
19081
+ // src/ai/rag/accessControl.ts
19082
+ var createRAGAccessControl = (options) => {
19083
+ const authorize = options.authorize;
19084
+ const contextCache = new WeakMap;
19085
+ const resolveScope = options.resolveScope;
19086
+ const loadContext = (request) => {
19087
+ const existing = contextCache.get(request);
19088
+ if (existing) {
19089
+ return existing;
19090
+ }
19091
+ const next = Promise.resolve(options.resolveContext(request));
19092
+ contextCache.set(request, next);
19093
+ return next;
19094
+ };
19095
+ return {
19096
+ authorizeRAGAction: authorize ? async (input) => authorize({
19097
+ ...input,
19098
+ context: await loadContext(input.request)
19099
+ }) : undefined,
19100
+ resolveRAGAccessScope: resolveScope ? async (request) => resolveScope({
19101
+ context: await loadContext(request),
19102
+ request
19103
+ }) : undefined
19104
+ };
19105
+ };
18274
19106
  // src/ai/rag/embeddingProviders.ts
18275
19107
  var DEFAULT_OPENAI_BASE_URL = "https://api.openai.com";
18276
19108
  var DEFAULT_GEMINI_BASE_URL = "https://generativelanguage.googleapis.com";
@@ -19128,6 +19960,8 @@ var isManagedBySyncSource = (document, sourceId) => document.metadata?.syncSourc
19128
19960
  var getDocumentSyncFingerprint = (document) => typeof document.metadata?.syncFingerprint === "string" ? document.metadata.syncFingerprint : undefined;
19129
19961
  var reconcileManagedDocuments = async (input) => {
19130
19962
  const prepared = prepareRAGDocuments({
19963
+ chunkingRegistry: input.chunkingRegistry,
19964
+ defaultChunking: input.defaultChunking,
19131
19965
  documents: input.documents
19132
19966
  });
19133
19967
  const nextDocumentIds = new Set(prepared.map((document) => document.documentId));
@@ -19172,6 +20006,19 @@ var toSourceRecord = (source, overrides) => ({
19172
20006
  target: source.target,
19173
20007
  ...overrides
19174
20008
  });
20009
+ var recoverSyncSourceRecord = (source, record, recoveredAt) => record.status === "running" ? toSourceRecord(source, {
20010
+ ...record,
20011
+ lastError: record.lastError ?? "Interrupted before completion during recovery",
20012
+ lastSyncedAt: recoveredAt,
20013
+ nextRetryAt: undefined,
20014
+ status: "failed"
20015
+ }) : toSourceRecord(source, {
20016
+ ...record,
20017
+ metadata: {
20018
+ ...source.metadata ?? {},
20019
+ ...record.metadata ?? {}
20020
+ }
20021
+ });
19175
20022
  var createRAGDirectorySyncSource = (options) => ({
19176
20023
  description: options.description,
19177
20024
  id: options.id,
@@ -19185,14 +20032,18 @@ var createRAGDirectorySyncSource = (options) => ({
19185
20032
  const loaded = await loadRAGDocumentsFromDirectory({
19186
20033
  baseMetadata: options.baseMetadata,
19187
20034
  defaultChunking: options.defaultChunking,
20035
+ chunkingRegistry: options.chunkingRegistry,
19188
20036
  directory: options.directory,
19189
20037
  extractors: options.extractors,
20038
+ extractorRegistry: options.extractorRegistry,
19190
20039
  includeExtensions: options.includeExtensions,
19191
20040
  recursive: options.recursive
19192
20041
  });
19193
20042
  const managedDocuments = loaded.documents.map((document) => toManagedSyncDocument(options.id, document, typeof document.metadata?.relativePath === "string" ? document.metadata.relativePath : document.source ?? document.title ?? ""));
19194
20043
  const reconciled = await reconcileManagedDocuments({
20044
+ chunkingRegistry: options.chunkingRegistry,
19195
20045
  collection,
20046
+ defaultChunking: options.defaultChunking,
19196
20047
  deleteDocument,
19197
20048
  documents: managedDocuments,
19198
20049
  listDocuments,
@@ -19223,12 +20074,16 @@ var createRAGUrlSyncSource = (options) => ({
19223
20074
  const loaded = await loadRAGDocumentsFromURLs({
19224
20075
  baseMetadata: options.baseMetadata,
19225
20076
  defaultChunking: options.defaultChunking,
20077
+ chunkingRegistry: options.chunkingRegistry,
19226
20078
  extractors: options.extractors,
20079
+ extractorRegistry: options.extractorRegistry,
19227
20080
  urls: options.urls
19228
20081
  });
19229
20082
  const managedDocuments = loaded.documents.map((document) => toManagedSyncDocument(options.id, document, typeof document.metadata?.sourceUrl === "string" ? document.metadata.sourceUrl : document.source ?? document.title ?? ""));
19230
20083
  const reconciled = await reconcileManagedDocuments({
20084
+ chunkingRegistry: options.chunkingRegistry,
19231
20085
  collection,
20086
+ defaultChunking: options.defaultChunking,
19232
20087
  deleteDocument,
19233
20088
  documents: managedDocuments,
19234
20089
  listDocuments,
@@ -19326,12 +20181,16 @@ var createRAGStorageSyncSource = (options) => ({
19326
20181
  const loaded = await loadRAGDocumentsFromUploads({
19327
20182
  baseMetadata: options.baseMetadata,
19328
20183
  defaultChunking: options.defaultChunking,
20184
+ chunkingRegistry: options.chunkingRegistry,
19329
20185
  extractors: options.extractors,
20186
+ extractorRegistry: options.extractorRegistry,
19330
20187
  uploads
19331
20188
  });
19332
20189
  const managedDocuments = loaded.documents.map((document) => toManagedSyncDocument(options.id, document, typeof document.metadata?.storageKey === "string" ? document.metadata.storageKey : document.source ?? document.title ?? ""));
19333
20190
  const reconciled = await reconcileManagedDocuments({
20191
+ chunkingRegistry: options.chunkingRegistry,
19334
20192
  collection,
20193
+ defaultChunking: options.defaultChunking,
19335
20194
  deleteDocument,
19336
20195
  documents: managedDocuments,
19337
20196
  listDocuments,
@@ -19412,7 +20271,9 @@ var createRAGEmailSyncSource = (options) => ({
19412
20271
  const loadedAttachments = attachmentUploads.length > 0 ? await loadRAGDocumentsFromUploads({
19413
20272
  baseMetadata: options.baseMetadata,
19414
20273
  defaultChunking: options.defaultChunking,
20274
+ chunkingRegistry: options.chunkingRegistry,
19415
20275
  extractors: options.extractors,
20276
+ extractorRegistry: options.extractorRegistry,
19416
20277
  uploads: attachmentUploads
19417
20278
  }) : { documents: [] };
19418
20279
  const managedDocuments = [
@@ -19420,7 +20281,9 @@ var createRAGEmailSyncSource = (options) => ({
19420
20281
  ...loadedAttachments.documents.map((document) => toManagedSyncDocument(options.id, document, `attachment:${String(document.metadata?.attachmentId ?? document.source ?? document.title ?? "")}`))
19421
20282
  ];
19422
20283
  const reconciled = await reconcileManagedDocuments({
20284
+ chunkingRegistry: options.chunkingRegistry,
19423
20285
  collection,
20286
+ defaultChunking: options.defaultChunking,
19424
20287
  deleteDocument,
19425
20288
  documents: managedDocuments,
19426
20289
  listDocuments,
@@ -19455,22 +20318,18 @@ var createRAGSyncManager = (options) => {
19455
20318
  }
19456
20319
  if (!hydrationPromise) {
19457
20320
  hydrationPromise = Promise.resolve(options.loadState()).then((records) => {
20321
+ const recoveredAt = Date.now();
19458
20322
  for (const record of records ?? []) {
19459
20323
  const source = sourceMap.get(record.id);
19460
20324
  if (!source) {
19461
20325
  continue;
19462
20326
  }
19463
- state.set(record.id, toSourceRecord(source, {
19464
- ...record,
19465
- metadata: {
19466
- ...source.metadata ?? {},
19467
- ...record.metadata ?? {}
19468
- }
19469
- }));
20327
+ state.set(record.id, recoverSyncSourceRecord(source, record, recoveredAt));
19470
20328
  }
19471
20329
  });
19472
20330
  }
19473
20331
  await hydrationPromise;
20332
+ await persistState();
19474
20333
  };
19475
20334
  const resolveRetryAttempts = (source) => Math.max(0, source.retryAttempts ?? options.retryAttempts ?? 0);
19476
20335
  const resolveRetryDelayMs = (source) => Math.max(0, source.retryDelayMs ?? options.retryDelayMs ?? 0);
@@ -19716,6 +20575,48 @@ var createRAGSyncScheduler = (input) => {
19716
20575
  listSchedules: () => [...input.schedules]
19717
20576
  };
19718
20577
  };
20578
+ // src/ai/rag/jobState.ts
20579
+ import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
20580
+ import { dirname as dirname3, resolve as resolve3 } from "path";
20581
+ var parseJobState = (content) => {
20582
+ try {
20583
+ const parsed = JSON.parse(content);
20584
+ return {
20585
+ adminActions: Array.isArray(parsed.adminActions) ? parsed.adminActions : [],
20586
+ adminJobs: Array.isArray(parsed.adminJobs) ? parsed.adminJobs : [],
20587
+ ingestJobs: Array.isArray(parsed.ingestJobs) ? parsed.ingestJobs : [],
20588
+ syncJobs: Array.isArray(parsed.syncJobs) ? parsed.syncJobs : []
20589
+ };
20590
+ } catch {
20591
+ return {
20592
+ adminActions: [],
20593
+ adminJobs: [],
20594
+ ingestJobs: [],
20595
+ syncJobs: []
20596
+ };
20597
+ }
20598
+ };
20599
+ var createRAGFileJobStateStore = (path) => {
20600
+ const resolvedPath = resolve3(path);
20601
+ return {
20602
+ load: async () => {
20603
+ try {
20604
+ return parseJobState(await readFile4(resolvedPath, "utf8"));
20605
+ } catch {
20606
+ return {
20607
+ adminActions: [],
20608
+ adminJobs: [],
20609
+ ingestJobs: [],
20610
+ syncJobs: []
20611
+ };
20612
+ }
20613
+ },
20614
+ save: async (state) => {
20615
+ await mkdir3(dirname3(resolvedPath), { recursive: true });
20616
+ await writeFile3(resolvedPath, JSON.stringify(state, null, 2), "utf8");
20617
+ }
20618
+ };
20619
+ };
19719
20620
  // src/ai/rag/adapters/utils.ts
19720
20621
  var vectorDimensionDefault = 24;
19721
20622
  var createRAGVector = (text, dimensions = vectorDimensionDefault) => {
@@ -19874,7 +20775,7 @@ import { existsSync as existsSync2 } from "fs";
19874
20775
  // src/ai/rag/resolveAbsoluteSQLiteVec.ts
19875
20776
  import { existsSync, readFileSync } from "fs";
19876
20777
  import { arch, platform } from "os";
19877
- import { dirname as dirname3, join as join2 } from "path";
20778
+ import { dirname as dirname4, join as join2 } from "path";
19878
20779
  var PLATFORM_PACKAGE_MAP = {
19879
20780
  "darwin-arm64": {
19880
20781
  libraryFile: "vec0.dylib",
@@ -19923,12 +20824,12 @@ var resolveAbsoluteSQLiteVec = () => {
19923
20824
  };
19924
20825
  }
19925
20826
  try {
19926
- const resolve3 = import.meta.resolve;
19927
- if (typeof resolve3 !== "function") {
20827
+ const resolve4 = import.meta.resolve;
20828
+ if (typeof resolve4 !== "function") {
19928
20829
  throw new Error("AbsoluteJS sqlite-vec package resolution requires import.meta.resolve support.");
19929
20830
  }
19930
- const packageJsonPath = new URL(resolve3(`${packageInfo.packageName}/package.json`)).pathname;
19931
- const packageRoot = dirname3(packageJsonPath);
20831
+ const packageJsonPath = new URL(resolve4(`${packageInfo.packageName}/package.json`)).pathname;
20832
+ const packageRoot = dirname4(packageJsonPath);
19932
20833
  const libraryPath = join2(packageRoot, packageInfo.libraryFile);
19933
20834
  const packageVersion = readPackageVersion(packageJsonPath);
19934
20835
  if (!existsSync(libraryPath)) {
@@ -20629,73 +21530,73 @@ var createConversationManager = () => {
20629
21530
  };
20630
21531
  };
20631
21532
  // src/ai/client/actions.ts
20632
- var serverMessageToAction = (msg) => {
20633
- switch (msg.type) {
21533
+ var serverMessageToAction = (message) => {
21534
+ switch (message.type) {
20634
21535
  case "chunk":
20635
21536
  return {
20636
- content: msg.content,
20637
- conversationId: msg.conversationId,
20638
- messageId: msg.messageId,
21537
+ content: message.content,
21538
+ conversationId: message.conversationId,
21539
+ messageId: message.messageId,
20639
21540
  type: "chunk"
20640
21541
  };
20641
21542
  case "thinking":
20642
21543
  return {
20643
- content: msg.content,
20644
- conversationId: msg.conversationId,
20645
- messageId: msg.messageId,
21544
+ content: message.content,
21545
+ conversationId: message.conversationId,
21546
+ messageId: message.messageId,
20646
21547
  type: "thinking"
20647
21548
  };
20648
21549
  case "tool_status":
20649
21550
  return {
20650
- conversationId: msg.conversationId,
20651
- input: msg.input,
20652
- messageId: msg.messageId,
20653
- name: msg.name,
20654
- result: msg.result,
20655
- status: msg.status,
21551
+ conversationId: message.conversationId,
21552
+ input: message.input,
21553
+ messageId: message.messageId,
21554
+ name: message.name,
21555
+ result: message.result,
21556
+ status: message.status,
20656
21557
  type: "tool_status"
20657
21558
  };
20658
21559
  case "image":
20659
21560
  return {
20660
- conversationId: msg.conversationId,
20661
- data: msg.data,
20662
- format: msg.format,
20663
- imageId: msg.imageId,
20664
- isPartial: msg.isPartial,
20665
- messageId: msg.messageId,
20666
- revisedPrompt: msg.revisedPrompt,
21561
+ conversationId: message.conversationId,
21562
+ data: message.data,
21563
+ format: message.format,
21564
+ imageId: message.imageId,
21565
+ isPartial: message.isPartial,
21566
+ messageId: message.messageId,
21567
+ revisedPrompt: message.revisedPrompt,
20667
21568
  type: "image"
20668
21569
  };
20669
21570
  case "complete":
20670
21571
  return {
20671
- conversationId: msg.conversationId,
20672
- durationMs: msg.durationMs,
20673
- messageId: msg.messageId,
20674
- model: msg.model,
20675
- sources: msg.sources,
21572
+ conversationId: message.conversationId,
21573
+ durationMs: message.durationMs,
21574
+ messageId: message.messageId,
21575
+ model: message.model,
21576
+ sources: message.sources,
20676
21577
  type: "complete",
20677
- usage: msg.usage
21578
+ usage: message.usage
20678
21579
  };
20679
21580
  case "rag_retrieving":
20680
21581
  return {
20681
- conversationId: msg.conversationId,
20682
- messageId: msg.messageId,
20683
- retrievalStartedAt: msg.retrievalStartedAt,
21582
+ conversationId: message.conversationId,
21583
+ messageId: message.messageId,
21584
+ retrievalStartedAt: message.retrievalStartedAt,
20684
21585
  type: "rag_retrieving"
20685
21586
  };
20686
21587
  case "rag_retrieved":
20687
21588
  return {
20688
- conversationId: msg.conversationId,
20689
- messageId: msg.messageId,
20690
- retrievalDurationMs: msg.retrievalDurationMs,
20691
- retrievalStartedAt: msg.retrievalStartedAt,
20692
- retrievedAt: msg.retrievedAt,
20693
- sources: msg.sources,
20694
- trace: msg.trace,
21589
+ conversationId: message.conversationId,
21590
+ messageId: message.messageId,
21591
+ retrievalDurationMs: message.retrievalDurationMs,
21592
+ retrievalStartedAt: message.retrievalStartedAt,
21593
+ retrievedAt: message.retrievedAt,
21594
+ sources: message.sources,
21595
+ trace: message.trace,
20695
21596
  type: "rag_retrieved"
20696
21597
  };
20697
21598
  case "error":
20698
- return { message: msg.message, type: "error" };
21599
+ return { message: message.message, type: "error" };
20699
21600
  default:
20700
21601
  return null;
20701
21602
  }
@@ -21154,8 +22055,8 @@ var createAIStream = (path, conversationId) => {
21154
22055
  listeners.forEach((listener) => listener());
21155
22056
  };
21156
22057
  const unsubscribeStore = store.subscribe(syncState);
21157
- const unsubscribeConnection = connection.subscribe((msg) => {
21158
- const action = serverMessageToAction(msg);
22058
+ const unsubscribeConnection = connection.subscribe((message) => {
22059
+ const action = serverMessageToAction(message);
21159
22060
  if (action) {
21160
22061
  store.dispatch(action);
21161
22062
  }
@@ -22514,6 +23415,8 @@ export {
22514
23415
  createRAGFileRetrievalComparisonHistoryStore,
22515
23416
  createRAGFileRetrievalBaselineStore,
22516
23417
  createRAGFileRetrievalBaselineGatePolicyHistoryStore,
23418
+ createRAGFileJobStateStore,
23419
+ createRAGFileExtractorRegistry,
22517
23420
  createRAGFileExtractor,
22518
23421
  createRAGFileEvaluationHistoryStore,
22519
23422
  createRAGFileAnswerGroundingEvaluationHistoryStore,
@@ -22524,9 +23427,11 @@ export {
22524
23427
  createRAGDirectorySyncSource,
22525
23428
  createRAGCollection,
22526
23429
  createRAGClient,
23430
+ createRAGChunkingRegistry,
22527
23431
  createRAGBunS3SyncClient,
22528
23432
  createRAGArchiveFileExtractor,
22529
23433
  createRAGArchiveExpander,
23434
+ createRAGAccessControl,
22530
23435
  createPDFFileExtractor,
22531
23436
  createOfficeDocumentExtractor,
22532
23437
  createMemoryStore,
@@ -22569,5 +23474,5 @@ export {
22569
23474
  aiChat
22570
23475
  };
22571
23476
 
22572
- //# debugId=2EBD38302AA26EE564756E2164756E21
23477
+ //# debugId=01B4F90C8AC5AD4364756E2164756E21
22573
23478
  //# sourceMappingURL=index.js.map