@shadowforge0/aquifer-memory 1.8.1 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { resolveApplicableRecords } = require('./memory-bootstrap');
4
+ const { assertAllowedScopeRequest } = require('./memory-serving');
4
5
  const { hybridRank } = require('./hybrid-rank');
5
6
 
6
7
  const TYPE_RANK = {
@@ -104,6 +105,7 @@ function isActiveVisible(record, opts = {}) {
104
105
  }
105
106
 
106
107
  function activeScopeKeys(opts = {}) {
108
+ assertAllowedScopeRequest(opts);
107
109
  if (Array.isArray(opts.activeScopePath) && opts.activeScopePath.length > 0) {
108
110
  return opts.activeScopePath.map(value => String(value)).filter(Boolean);
109
111
  }
@@ -274,6 +276,26 @@ function recallMemoryRecords(records = [], query, opts = {}) {
274
276
  }
275
277
 
276
278
  function createMemoryRecall({ pool, schema, defaultTenantId }) {
279
+ async function withResolvedScopeKey(opts = {}) {
280
+ const next = { ...opts };
281
+ if (!next.scopeId) return next;
282
+ const tenantId = next.tenantId || defaultTenantId;
283
+ const result = await pool.query(
284
+ `SELECT scope_key FROM ${schema}.scopes WHERE tenant_id = $1 AND id = $2 LIMIT 1`,
285
+ [tenantId, next.scopeId],
286
+ );
287
+ const resolvedScopeKey = result.rows?.[0]?.scope_key || null;
288
+ if (resolvedScopeKey) {
289
+ next.resolvedScopeKey = resolvedScopeKey;
290
+ if (!next.activeScopeKey && !next.activeScopePath) {
291
+ next.activeScopePath = [resolvedScopeKey];
292
+ next.activeScopeKey = resolvedScopeKey;
293
+ }
294
+ }
295
+ assertAllowedScopeRequest(next);
296
+ return next;
297
+ }
298
+
277
299
  function applyCurrentMemoryFilters(where, params, opts = {}) {
278
300
  const scopeKeys = activeScopeKeys(opts);
279
301
  if (opts.scopeId) {
@@ -295,12 +317,13 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
295
317
  }
296
318
 
297
319
  async function recall(query, opts = {}) {
320
+ const scopedOpts = await withResolvedScopeKey(opts);
298
321
  const q = String(query || '').trim();
299
322
  if (!q) throw new Error('memory.recall(query): query must be a non-empty string');
300
- const tenantId = opts.tenantId || defaultTenantId;
301
- const limit = Math.max(1, Math.min(50, opts.limit || 10));
302
- const cfg = (opts.ftsConfig === 'zhcfg' || opts.ftsConfig === 'simple') ? opts.ftsConfig : 'simple';
303
- const scopeKeys = activeScopeKeys(opts);
323
+ const tenantId = scopedOpts.tenantId || defaultTenantId;
324
+ const limit = Math.max(1, Math.min(50, scopedOpts.limit || 10));
325
+ const cfg = (scopedOpts.ftsConfig === 'zhcfg' || scopedOpts.ftsConfig === 'simple') ? scopedOpts.ftsConfig : 'simple';
326
+ const scopeKeys = activeScopeKeys(scopedOpts);
304
327
  const fetchLimit = Math.max(limit, Math.min(200, scopeKeys ? limit * 4 : limit));
305
328
  const feedbackScoreExpr = feedbackScoreSql(schema);
306
329
  const params = [tenantId, q];
@@ -314,7 +337,7 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
314
337
  OR m.context_key ILIKE '%' || $2 || '%'
315
338
  OR m.topic_key ILIKE '%' || $2 || '%')`,
316
339
  ];
317
- applyCurrentMemoryFilters(where, params, opts);
340
+ applyCurrentMemoryFilters(where, params, scopedOpts);
318
341
  params.push(fetchLimit);
319
342
  const result = await pool.query(
320
343
  `SELECT
@@ -340,7 +363,7 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
340
363
  params
341
364
  );
342
365
  const applicableRows = scopeKeys
343
- ? resolveApplicableRecords(result.rows, opts)
366
+ ? resolveApplicableRecords(result.rows, scopedOpts)
344
367
  : result.rows;
345
368
  return applicableRows
346
369
  .sort(sortRecallRows)
@@ -348,11 +371,12 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
348
371
  }
349
372
 
350
373
  async function recallViaEvidenceItems(query, opts = {}) {
374
+ const scopedOpts = await withResolvedScopeKey(opts);
351
375
  const q = String(query || '').trim();
352
376
  if (!q) throw new Error('memory.recall(query): query must be a non-empty string');
353
- const tenantId = opts.tenantId || defaultTenantId;
354
- const limit = Math.max(1, Math.min(50, opts.limit || 10));
355
- const scopeKeys = activeScopeKeys(opts);
377
+ const tenantId = scopedOpts.tenantId || defaultTenantId;
378
+ const limit = Math.max(1, Math.min(50, scopedOpts.limit || 10));
379
+ const scopeKeys = activeScopeKeys(scopedOpts);
356
380
  const fetchLimit = Math.max(limit, Math.min(200, scopeKeys ? limit * 4 : limit));
357
381
  const feedbackScoreExpr = feedbackScoreSql(schema);
358
382
  const params = [tenantId, q];
@@ -361,8 +385,8 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
361
385
  `m.status = 'active'`,
362
386
  `m.visible_in_recall = true`,
363
387
  ];
364
- applyCurrentMemoryFilters(where, params, opts);
365
- const queryVec = vecToStr(opts.queryVec);
388
+ applyCurrentMemoryFilters(where, params, scopedOpts);
389
+ const queryVec = vecToStr(scopedOpts.queryVec);
366
390
  let vectorScoreExpr = '0';
367
391
  let evidencePredicate = `(ei.excerpt_text ILIKE '%' || $2 || '%'
368
392
  OR ei.search_tsv @@ plainto_tsquery('simple', $2))`;
@@ -370,7 +394,7 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
370
394
  params.push(queryVec);
371
395
  const vecPos = params.length;
372
396
  vectorScoreExpr = `COALESCE(1.0 - (ei.embedding <=> $${vecPos}::vector), 0)`;
373
- evidencePredicate = opts.vectorOnly === true
397
+ evidencePredicate = scopedOpts.vectorOnly === true
374
398
  ? `ei.embedding IS NOT NULL`
375
399
  : `(${evidencePredicate} OR ei.embedding IS NOT NULL)`;
376
400
  }
@@ -425,7 +449,7 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
425
449
  params,
426
450
  );
427
451
  const applicableRows = scopeKeys
428
- ? resolveApplicableRecords(result.rows, opts)
452
+ ? resolveApplicableRecords(result.rows, scopedOpts)
429
453
  : result.rows;
430
454
  return applicableRows
431
455
  .sort(sortRecallRows)
@@ -433,11 +457,12 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
433
457
  }
434
458
 
435
459
  async function recallViaMemoryEmbeddings(queryVec, opts = {}) {
460
+ const scopedOpts = await withResolvedScopeKey(opts);
436
461
  const vector = vecToStr(queryVec);
437
462
  if (!vector) return [];
438
- const tenantId = opts.tenantId || defaultTenantId;
439
- const limit = Math.max(1, Math.min(50, opts.limit || 10));
440
- const scopeKeys = activeScopeKeys(opts);
463
+ const tenantId = scopedOpts.tenantId || defaultTenantId;
464
+ const limit = Math.max(1, Math.min(50, scopedOpts.limit || 10));
465
+ const scopeKeys = activeScopeKeys(scopedOpts);
441
466
  const fetchLimit = Math.max(limit, Math.min(200, scopeKeys ? limit * 4 : limit));
442
467
  const feedbackScoreExpr = feedbackScoreSql(schema);
443
468
  const params = [tenantId, vector];
@@ -447,7 +472,7 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
447
472
  `m.visible_in_recall = true`,
448
473
  `m.embedding IS NOT NULL`,
449
474
  ];
450
- applyCurrentMemoryFilters(where, params, opts);
475
+ applyCurrentMemoryFilters(where, params, scopedOpts);
451
476
  params.push(fetchLimit);
452
477
  const result = await pool.query(
453
478
  `SELECT
@@ -473,7 +498,7 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
473
498
  params,
474
499
  );
475
500
  const applicableRows = scopeKeys
476
- ? resolveApplicableRecords(result.rows, opts)
501
+ ? resolveApplicableRecords(result.rows, scopedOpts)
477
502
  : result.rows;
478
503
  return applicableRows
479
504
  .sort(sortRecallRows)
@@ -481,11 +506,12 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
481
506
  }
482
507
 
483
508
  async function recallViaLinkedSummaryEmbeddings(queryVec, opts = {}) {
509
+ const scopedOpts = await withResolvedScopeKey(opts);
484
510
  const vector = vecToStr(queryVec);
485
511
  if (!vector) return [];
486
- const tenantId = opts.tenantId || defaultTenantId;
487
- const limit = Math.max(1, Math.min(50, opts.limit || 10));
488
- const scopeKeys = activeScopeKeys(opts);
512
+ const tenantId = scopedOpts.tenantId || defaultTenantId;
513
+ const limit = Math.max(1, Math.min(50, scopedOpts.limit || 10));
514
+ const scopeKeys = activeScopeKeys(scopedOpts);
489
515
  const fetchLimit = Math.max(limit, Math.min(200, scopeKeys ? limit * 4 : limit));
490
516
  const feedbackScoreExpr = feedbackScoreSql(schema);
491
517
  const params = [tenantId, vector];
@@ -494,7 +520,7 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
494
520
  `m.status = 'active'`,
495
521
  `m.visible_in_recall = true`,
496
522
  ];
497
- applyCurrentMemoryFilters(where, params, opts);
523
+ applyCurrentMemoryFilters(where, params, scopedOpts);
498
524
  params.push(fetchLimit);
499
525
  const result = await pool.query(
500
526
  `WITH eligible_memories AS (
@@ -543,7 +569,7 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
543
569
  params,
544
570
  );
545
571
  const applicableRows = scopeKeys
546
- ? resolveApplicableRecords(result.rows, opts)
572
+ ? resolveApplicableRecords(result.rows, scopedOpts)
547
573
  : result.rows;
548
574
  return applicableRows
549
575
  .sort(sortRecallRows)
@@ -2,6 +2,7 @@
2
2
 
3
3
  const crypto = require('crypto');
4
4
  const { resolveApplicableRecords } = require('./memory-bootstrap');
5
+ const { assertAllowedScopeRequest } = require('./memory-serving');
5
6
 
6
7
  function requireField(obj, field) {
7
8
  if (!obj || obj[field] === undefined || obj[field] === null || obj[field] === '') {
@@ -773,17 +774,27 @@ function createMemoryRecords({ pool, schema, defaultTenantId, inTransaction = fa
773
774
  const tenantId = input.tenantId || defaultTenantId;
774
775
  let activeScopePath = normalizeScopePath(input.activeScopePath, input.activeScopeKey);
775
776
  let activeScopeKey = input.activeScopeKey || activeScopePath[activeScopePath.length - 1] || null;
776
- if (input.scopeId && !input.activeScopeKey && !input.activeScopePath) {
777
+ let resolvedScopeKey = null;
778
+ if (input.scopeId) {
777
779
  const scopeResult = await pool.query(
778
780
  `SELECT scope_key FROM ${scopes} WHERE tenant_id = $1 AND id = $2 LIMIT 1`,
779
781
  [tenantId, input.scopeId],
780
782
  );
781
- const scopedKey = scopeResult.rows[0]?.scope_key || null;
782
- if (scopedKey) {
783
- activeScopePath = [scopedKey];
784
- activeScopeKey = scopedKey;
783
+ resolvedScopeKey = scopeResult.rows[0]?.scope_key || null;
784
+ if (resolvedScopeKey && !input.activeScopeKey && !input.activeScopePath) {
785
+ activeScopePath = [resolvedScopeKey];
786
+ activeScopeKey = resolvedScopeKey;
785
787
  }
786
788
  }
789
+ assertAllowedScopeRequest({
790
+ ...input,
791
+ activeScopeKey,
792
+ activeScopePath,
793
+ resolvedScopeKey,
794
+ });
795
+ if (input.includeEvidenceRefs === true && input.operator !== true && input.internal !== true) {
796
+ throw new Error('memory.current includeEvidenceRefs requires operator=true or internal=true');
797
+ }
787
798
  const limit = Math.max(1, Math.min(100, input.limit || 50));
788
799
  const fetchLimit = Math.max(limit + 1, Math.min(200, Math.max(limit * 4, 40)));
789
800
  const asOf = input.asOf || new Date().toISOString();