@remnic/core 9.3.761 → 9.3.763
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-admin-ops-surface.js +10 -9
- package/dist/access-boundary.js +11 -10
- package/dist/access-cli.js +45 -44
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.js +17 -16
- package/dist/access-identity-continuity-surface.js +10 -9
- package/dist/access-lcm-surface.js +10 -9
- package/dist/access-mcp.js +14 -13
- package/dist/access-observe-write-surface.js +10 -9
- package/dist/access-operations-batch.js +12 -11
- package/dist/access-operations.js +13 -12
- package/dist/access-recall-surface.js +10 -9
- package/dist/access-service.js +10 -9
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/briefing.js +5 -4
- package/dist/causal-consolidation.js +6 -5
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/{chunk-742MWWVL.js → chunk-2RLECZ6J.js} +2 -2
- package/dist/{chunk-CFHDI5CV.js → chunk-6YEENRE3.js} +4 -4
- package/dist/{chunk-JYH3XYWM.js → chunk-6ZFE2TPO.js} +3 -3
- package/dist/{chunk-LG2MCUBC.js → chunk-A4FMQIST.js} +2 -2
- package/dist/{chunk-APJQ6UEA.js → chunk-AGNBY3VG.js} +4 -4
- package/dist/{chunk-OCPVA4TH.js → chunk-AS2NRB45.js} +2 -2
- package/dist/{chunk-W4ADKEIY.js → chunk-BAF7MBEH.js} +22 -22
- package/dist/{chunk-ICVNVSKN.js → chunk-BRYPUVJU.js} +2 -2
- package/dist/{chunk-IV4INTAU.js → chunk-CVINPTKG.js} +2 -2
- package/dist/{chunk-IRFHFRHP.js → chunk-D6YTILJO.js} +25 -25
- package/dist/chunk-DQC3ZZYM.js +35 -0
- package/dist/chunk-DQC3ZZYM.js.map +1 -0
- package/dist/{chunk-3DAIQSVS.js → chunk-DTTVX6EO.js} +23 -8
- package/dist/chunk-DTTVX6EO.js.map +1 -0
- package/dist/{chunk-P7UA66OG.js → chunk-IVFIYAIP.js} +2 -2
- package/dist/{chunk-RKNJBZ55.js → chunk-JBPKEARU.js} +4 -4
- package/dist/{chunk-Y3XEN5TL.js → chunk-K4EDULCP.js} +3 -3
- package/dist/{chunk-P7N2DQLC.js → chunk-KXG7HHIQ.js} +3 -3
- package/dist/{chunk-4WOXD56B.js → chunk-LDCFFJ67.js} +5 -5
- package/dist/{chunk-5AZQZFJG.js → chunk-N3QDLE2N.js} +5 -5
- package/dist/{chunk-2B3BTQN4.js → chunk-N4ZA2I7M.js} +2 -2
- package/dist/chunk-NDXJJKT2.js +118 -0
- package/dist/chunk-NDXJJKT2.js.map +1 -0
- package/dist/{chunk-HP23UD6W.js → chunk-NFWTMM2M.js} +2 -2
- package/dist/{chunk-3QLPBCFG.js → chunk-NKP6II6G.js} +4 -4
- package/dist/{chunk-HHCMZEUC.js → chunk-OX272PBW.js} +2 -2
- package/dist/{chunk-YU4CLTZY.js → chunk-QPTP6SKC.js} +2 -2
- package/dist/{chunk-6S2C5FQ4.js → chunk-R3K4CXPR.js} +2 -2
- package/dist/{chunk-XDFWMY4X.js → chunk-SFNQDXXL.js} +108 -14
- package/dist/chunk-SFNQDXXL.js.map +1 -0
- package/dist/{chunk-MB5RSUW6.js → chunk-SXN3LCUT.js} +114 -28
- package/dist/chunk-SXN3LCUT.js.map +1 -0
- package/dist/{chunk-3CBSKDRL.js → chunk-TIOXQPD7.js} +9 -9
- package/dist/{chunk-CXW6Y7SI.js → chunk-V5RVMULT.js} +4 -4
- package/dist/{chunk-DUU4VLOW.js → chunk-VKHZFF5P.js} +2 -2
- package/dist/{chunk-NPZTEHR7.js → chunk-X7HTU4HO.js} +2 -2
- package/dist/{chunk-ZAIU7ZEA.js → chunk-YDYC6PIS.js} +2 -2
- package/dist/cli.js +34 -33
- package/dist/compounding/engine.js +5 -4
- package/dist/connectors/codex-materialize-runner.js +5 -4
- package/dist/connectors/index.js +5 -4
- package/dist/entity-retrieval.js +5 -4
- package/dist/{forget-OEE5OELK.js → forget-WYLR4ZFV.js} +2 -2
- package/dist/index.js +47 -46
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.js +4 -4
- package/dist/maintenance/memory-governance.js +5 -4
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +11 -1
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +5 -4
- package/dist/maintenance/rebuild-memory-projection.d.ts +22 -1
- package/dist/maintenance/rebuild-memory-projection.js +6 -5
- package/dist/memory-projection-store.d.ts +6 -2
- package/dist/memory-projection-store.js +9 -3
- package/dist/namespaces/migrate.js +9 -8
- package/dist/namespaces/search.js +3 -3
- package/dist/namespaces/storage.js +5 -4
- package/dist/operator-toolkit.js +12 -11
- package/dist/orchestration/maintenance.js +7 -6
- package/dist/orchestrator.js +45 -44
- package/dist/{purge-6ATBGT77.js → purge-IHRX4Z7X.js} +7 -1
- package/dist/purge-IHRX4Z7X.js.map +1 -0
- package/dist/schemas.d.ts +22 -22
- package/dist/search/factory.js +2 -2
- package/dist/search/index.js +4 -4
- package/dist/semantic-consolidation.js +6 -5
- package/dist/semantic-rule-promotion.js +5 -4
- package/dist/semantic-rule-verifier.js +5 -4
- package/dist/storage-guards.d.ts +9 -0
- package/dist/storage-guards.js +11 -0
- package/dist/storage-guards.js.map +1 -0
- package/dist/storage.js +4 -3
- package/dist/transfer/import-sqlite.js +2 -2
- package/dist/transfer/types.d.ts +12 -12
- package/dist/verified-recall.js +5 -4
- package/package.json +2 -2
- package/src/maintenance/purge.test.ts +76 -0
- package/src/maintenance/purge.ts +3 -0
- package/src/maintenance/rebuild-memory-lifecycle-ledger.ts +59 -8
- package/src/maintenance/rebuild-memory-projection.ts +154 -10
- package/src/memory-projection-store.ts +143 -35
- package/src/storage-guards.ts +30 -0
- package/src/storage.ts +16 -16
- package/dist/chunk-3DAIQSVS.js.map +0 -1
- package/dist/chunk-GYJESQS5.js +0 -77
- package/dist/chunk-GYJESQS5.js.map +0 -1
- package/dist/chunk-MB5RSUW6.js.map +0 -1
- package/dist/chunk-XDFWMY4X.js.map +0 -1
- package/dist/purge-6ATBGT77.js.map +0 -1
- /package/dist/{chunk-742MWWVL.js.map → chunk-2RLECZ6J.js.map} +0 -0
- /package/dist/{chunk-CFHDI5CV.js.map → chunk-6YEENRE3.js.map} +0 -0
- /package/dist/{chunk-JYH3XYWM.js.map → chunk-6ZFE2TPO.js.map} +0 -0
- /package/dist/{chunk-LG2MCUBC.js.map → chunk-A4FMQIST.js.map} +0 -0
- /package/dist/{chunk-APJQ6UEA.js.map → chunk-AGNBY3VG.js.map} +0 -0
- /package/dist/{chunk-OCPVA4TH.js.map → chunk-AS2NRB45.js.map} +0 -0
- /package/dist/{chunk-W4ADKEIY.js.map → chunk-BAF7MBEH.js.map} +0 -0
- /package/dist/{chunk-ICVNVSKN.js.map → chunk-BRYPUVJU.js.map} +0 -0
- /package/dist/{chunk-IV4INTAU.js.map → chunk-CVINPTKG.js.map} +0 -0
- /package/dist/{chunk-IRFHFRHP.js.map → chunk-D6YTILJO.js.map} +0 -0
- /package/dist/{chunk-P7UA66OG.js.map → chunk-IVFIYAIP.js.map} +0 -0
- /package/dist/{chunk-RKNJBZ55.js.map → chunk-JBPKEARU.js.map} +0 -0
- /package/dist/{chunk-Y3XEN5TL.js.map → chunk-K4EDULCP.js.map} +0 -0
- /package/dist/{chunk-P7N2DQLC.js.map → chunk-KXG7HHIQ.js.map} +0 -0
- /package/dist/{chunk-4WOXD56B.js.map → chunk-LDCFFJ67.js.map} +0 -0
- /package/dist/{chunk-5AZQZFJG.js.map → chunk-N3QDLE2N.js.map} +0 -0
- /package/dist/{chunk-2B3BTQN4.js.map → chunk-N4ZA2I7M.js.map} +0 -0
- /package/dist/{chunk-HP23UD6W.js.map → chunk-NFWTMM2M.js.map} +0 -0
- /package/dist/{chunk-3QLPBCFG.js.map → chunk-NKP6II6G.js.map} +0 -0
- /package/dist/{chunk-HHCMZEUC.js.map → chunk-OX272PBW.js.map} +0 -0
- /package/dist/{chunk-YU4CLTZY.js.map → chunk-QPTP6SKC.js.map} +0 -0
- /package/dist/{chunk-6S2C5FQ4.js.map → chunk-R3K4CXPR.js.map} +0 -0
- /package/dist/{chunk-3CBSKDRL.js.map → chunk-TIOXQPD7.js.map} +0 -0
- /package/dist/{chunk-CXW6Y7SI.js.map → chunk-V5RVMULT.js.map} +0 -0
- /package/dist/{chunk-DUU4VLOW.js.map → chunk-VKHZFF5P.js.map} +0 -0
- /package/dist/{chunk-NPZTEHR7.js.map → chunk-X7HTU4HO.js.map} +0 -0
- /package/dist/{chunk-ZAIU7ZEA.js.map → chunk-YDYC6PIS.js.map} +0 -0
- /package/dist/{forget-OEE5OELK.js.map → forget-WYLR4ZFV.js.map} +0 -0
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
type BetterSqlite3Database,
|
|
18
18
|
} from "./runtime/better-sqlite.js";
|
|
19
19
|
|
|
20
|
-
export const MEMORY_PROJECTION_SCHEMA_VERSION =
|
|
20
|
+
export const MEMORY_PROJECTION_SCHEMA_VERSION = 3;
|
|
21
21
|
|
|
22
22
|
export interface ProjectedMemoryBrowseOptions {
|
|
23
23
|
query?: string;
|
|
@@ -153,11 +153,13 @@ function migrateProjectionSchemaIfNeeded(memoryDir: string): void {
|
|
|
153
153
|
export function memoryCurrentSelectExpressions(db: BetterSqlite3Database): {
|
|
154
154
|
tagsJson: string;
|
|
155
155
|
previewText: string;
|
|
156
|
+
hasPathValid: boolean;
|
|
156
157
|
} {
|
|
157
158
|
const columns = listTableColumns(db, "memory_current");
|
|
158
159
|
return {
|
|
159
160
|
tagsJson: columns.has("tags_json") ? "tags_json" : `'[]' AS tags_json`,
|
|
160
161
|
previewText: columns.has("preview_text") ? "preview_text" : `'' AS preview_text`,
|
|
162
|
+
hasPathValid: columns.has("path_valid"),
|
|
161
163
|
};
|
|
162
164
|
}
|
|
163
165
|
|
|
@@ -174,6 +176,7 @@ export function initializeMemoryProjectionDb(db: BetterSqlite3Database): void {
|
|
|
174
176
|
status TEXT NOT NULL,
|
|
175
177
|
lifecycle_state TEXT,
|
|
176
178
|
path_rel TEXT NOT NULL,
|
|
179
|
+
path_valid INTEGER NOT NULL DEFAULT 0 CHECK (path_valid IN (0, 1)),
|
|
177
180
|
created_at TEXT NOT NULL,
|
|
178
181
|
updated_at TEXT NOT NULL,
|
|
179
182
|
archived_at TEXT,
|
|
@@ -195,8 +198,6 @@ export function initializeMemoryProjectionDb(db: BetterSqlite3Database): void {
|
|
|
195
198
|
CREATE INDEX IF NOT EXISTS idx_memory_current_category
|
|
196
199
|
ON memory_current(category);
|
|
197
200
|
|
|
198
|
-
CREATE INDEX IF NOT EXISTS idx_memory_current_updated
|
|
199
|
-
ON memory_current(updated_at DESC);
|
|
200
201
|
|
|
201
202
|
CREATE TABLE IF NOT EXISTS memory_timeline (
|
|
202
203
|
event_id TEXT PRIMARY KEY,
|
|
@@ -297,6 +298,18 @@ export function initializeMemoryProjectionDb(db: BetterSqlite3Database): void {
|
|
|
297
298
|
`);
|
|
298
299
|
|
|
299
300
|
migrateMemoryCurrentTable(db);
|
|
301
|
+
if (listTableColumns(db, "memory_current").has("path_valid")) {
|
|
302
|
+
db.exec(`
|
|
303
|
+
CREATE INDEX IF NOT EXISTS idx_memory_current_browse_updated_desc
|
|
304
|
+
ON memory_current(path_valid, updated_at DESC, created_at DESC, memory_id ASC);
|
|
305
|
+
CREATE INDEX IF NOT EXISTS idx_memory_current_browse_updated_asc
|
|
306
|
+
ON memory_current(path_valid, updated_at ASC, created_at ASC, memory_id ASC);
|
|
307
|
+
CREATE INDEX IF NOT EXISTS idx_memory_current_browse_created_desc
|
|
308
|
+
ON memory_current(path_valid, created_at DESC, updated_at DESC, memory_id ASC);
|
|
309
|
+
CREATE INDEX IF NOT EXISTS idx_memory_current_browse_created_asc
|
|
310
|
+
ON memory_current(path_valid, created_at ASC, updated_at ASC, memory_id ASC);
|
|
311
|
+
`);
|
|
312
|
+
}
|
|
300
313
|
db.prepare("INSERT OR REPLACE INTO meta(key, value) VALUES (?, ?)")
|
|
301
314
|
.run("schemaVersion", String(MEMORY_PROJECTION_SCHEMA_VERSION));
|
|
302
315
|
}
|
|
@@ -309,6 +322,42 @@ function openProjectionReadonly(memoryDir: string): BetterSqlite3Database | null
|
|
|
309
322
|
return null;
|
|
310
323
|
}
|
|
311
324
|
}
|
|
325
|
+
function updateProjectionBestEffort(
|
|
326
|
+
memoryDir: string,
|
|
327
|
+
sql: string,
|
|
328
|
+
params: unknown[],
|
|
329
|
+
): void {
|
|
330
|
+
let db: BetterSqlite3Database | null = null;
|
|
331
|
+
try {
|
|
332
|
+
db = openBetterSqlite3(getMemoryProjectionPath(memoryDir), { fileMustExist: true });
|
|
333
|
+
db.prepare(sql).run(...params);
|
|
334
|
+
} catch {
|
|
335
|
+
// Projection updates must never block the canonical filesystem mutation.
|
|
336
|
+
} finally {
|
|
337
|
+
db?.close();
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export function markProjectedMemoryPathInvalid(memoryDir: string, memoryId: string): void {
|
|
342
|
+
updateProjectionBestEffort(
|
|
343
|
+
memoryDir,
|
|
344
|
+
"UPDATE memory_current SET path_valid = 0 WHERE memory_id = ?",
|
|
345
|
+
[memoryId],
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export function updateProjectedMemoryPath(
|
|
350
|
+
memoryDir: string,
|
|
351
|
+
memoryId: string,
|
|
352
|
+
pathRel: string,
|
|
353
|
+
): void {
|
|
354
|
+
const pathValid = isProjectedMemoryPathValid(memoryDir, pathRel) ? 1 : 0;
|
|
355
|
+
updateProjectionBestEffort(
|
|
356
|
+
memoryDir,
|
|
357
|
+
"UPDATE memory_current SET path_rel = ?, path_valid = ? WHERE memory_id = ?",
|
|
358
|
+
[pathRel, pathValid, memoryId],
|
|
359
|
+
);
|
|
360
|
+
}
|
|
312
361
|
|
|
313
362
|
function withProjectionReadonly<T>(
|
|
314
363
|
memoryDir: string,
|
|
@@ -363,7 +412,11 @@ function isPathInsideRoot(candidatePath: string, rootPath: string): boolean {
|
|
|
363
412
|
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
364
413
|
}
|
|
365
414
|
|
|
366
|
-
function resolveProjectedMemoryPath(
|
|
415
|
+
function resolveProjectedMemoryPath(
|
|
416
|
+
memoryDir: string,
|
|
417
|
+
pathRel: string,
|
|
418
|
+
requireExisting = false,
|
|
419
|
+
): string | null {
|
|
367
420
|
if (path.isAbsolute(pathRel)) return null;
|
|
368
421
|
const root = resolveMemoryDirRoot(memoryDir);
|
|
369
422
|
const candidate = path.resolve(root, pathRel);
|
|
@@ -372,14 +425,21 @@ function resolveProjectedMemoryPath(memoryDir: string, pathRel: string): string
|
|
|
372
425
|
const realCandidate = fs.realpathSync(candidate);
|
|
373
426
|
if (!isPathInsideRoot(realCandidate, root)) return null;
|
|
374
427
|
} catch {
|
|
428
|
+
if (requireExisting) return null;
|
|
375
429
|
// Missing files are handled by callers. Still keep the lexical guard above.
|
|
376
430
|
}
|
|
377
431
|
return candidate;
|
|
378
432
|
}
|
|
379
433
|
|
|
434
|
+
export function isProjectedMemoryPathValid(memoryDir: string, pathRel: string): boolean {
|
|
435
|
+
return resolveProjectedMemoryPath(memoryDir, pathRel) !== null;
|
|
436
|
+
}
|
|
437
|
+
|
|
380
438
|
function projectedBrowseRowFromCurrentRow(
|
|
381
439
|
memoryDir: string,
|
|
382
440
|
row: Record<string, unknown>,
|
|
441
|
+
resolvedFilePath?: string,
|
|
442
|
+
requireExisting = false,
|
|
383
443
|
): ProjectedMemoryBrowseRow | null {
|
|
384
444
|
if (
|
|
385
445
|
typeof row.memory_id !== "string" ||
|
|
@@ -389,7 +449,7 @@ function projectedBrowseRowFromCurrentRow(
|
|
|
389
449
|
) {
|
|
390
450
|
return null;
|
|
391
451
|
}
|
|
392
|
-
const filePath = resolveProjectedMemoryPath(memoryDir, row.path_rel);
|
|
452
|
+
const filePath = resolvedFilePath ?? resolveProjectedMemoryPath(memoryDir, row.path_rel, requireExisting);
|
|
393
453
|
if (!filePath) return null;
|
|
394
454
|
return {
|
|
395
455
|
id: row.memory_id,
|
|
@@ -613,8 +673,15 @@ export function readProjectedMemoryBrowse(
|
|
|
613
673
|
const whereSql = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
|
|
614
674
|
|
|
615
675
|
if (normalizedQuery) {
|
|
616
|
-
//
|
|
617
|
-
|
|
676
|
+
// Full-content search still examines every eligible row so totals and
|
|
677
|
+
// matches stay exact, but iteration keeps memory use proportional to the page.
|
|
678
|
+
const queryWhereClauses = currentSelect.hasPathValid
|
|
679
|
+
? [...whereClauses, ...projectedBrowsePathSqlClauses(), "path_valid = 1"]
|
|
680
|
+
: whereClauses;
|
|
681
|
+
const queryWhereSql = queryWhereClauses.length > 0
|
|
682
|
+
? `WHERE ${queryWhereClauses.join(" AND ")}`
|
|
683
|
+
: "";
|
|
684
|
+
const rows = db
|
|
618
685
|
.prepare(`
|
|
619
686
|
SELECT
|
|
620
687
|
memory_id,
|
|
@@ -627,47 +694,88 @@ export function readProjectedMemoryBrowse(
|
|
|
627
694
|
${currentSelect.tagsJson},
|
|
628
695
|
${currentSelect.previewText}
|
|
629
696
|
FROM memory_current
|
|
630
|
-
${
|
|
697
|
+
${queryWhereSql}
|
|
631
698
|
ORDER BY ${orderBySql}
|
|
632
699
|
`)
|
|
633
|
-
.
|
|
700
|
+
.iterate(...params) as Iterable<Record<string, unknown>>;
|
|
701
|
+
const pageRows: ProjectedMemoryBrowseRow[] = [];
|
|
702
|
+
let total = 0;
|
|
634
703
|
|
|
635
|
-
const
|
|
636
|
-
if (typeof row.memory_id !== "string" || typeof row.path_rel !== "string")
|
|
637
|
-
const filePath = resolveProjectedMemoryPath(
|
|
638
|
-
|
|
639
|
-
|
|
704
|
+
for (const row of rows) {
|
|
705
|
+
if (typeof row.memory_id !== "string" || typeof row.path_rel !== "string") continue;
|
|
706
|
+
const filePath = resolveProjectedMemoryPath(
|
|
707
|
+
memoryDir,
|
|
708
|
+
row.path_rel,
|
|
709
|
+
currentSelect.hasPathValid,
|
|
710
|
+
);
|
|
711
|
+
if (!filePath) continue;
|
|
640
712
|
const preview = typeof row.preview_text === "string" ? row.preview_text.toLowerCase() : "";
|
|
641
713
|
const category = typeof row.category === "string" ? row.category.toLowerCase() : "";
|
|
642
714
|
const entityRef = typeof row.entity_ref === "string" ? row.entity_ref.toLowerCase() : "";
|
|
643
715
|
const tags = typeof row.tags_json === "string" ? row.tags_json.toLowerCase() : "";
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
716
|
+
let matches = preview.includes(normalizedQuery) || category.includes(normalizedQuery) ||
|
|
717
|
+
entityRef.includes(normalizedQuery) || tags.includes(normalizedQuery);
|
|
718
|
+
if (!matches) {
|
|
719
|
+
try {
|
|
720
|
+
matches = fs.readFileSync(filePath, "utf-8").toLowerCase().includes(normalizedQuery);
|
|
721
|
+
} catch {
|
|
722
|
+
matches = false;
|
|
723
|
+
}
|
|
647
724
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
} catch {
|
|
653
|
-
return false;
|
|
725
|
+
if (!matches) continue;
|
|
726
|
+
if (total >= options.offset && pageRows.length < options.limit) {
|
|
727
|
+
const browseRow = projectedBrowseRowFromCurrentRow(memoryDir, row, filePath);
|
|
728
|
+
if (browseRow) pageRows.push(browseRow);
|
|
654
729
|
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
return {
|
|
659
|
-
total: filtered.length,
|
|
660
|
-
memories: pageRows
|
|
661
|
-
.map((row) => projectedBrowseRowFromCurrentRow(memoryDir, row))
|
|
662
|
-
.filter((row): row is ProjectedMemoryBrowseRow => row !== null),
|
|
663
|
-
};
|
|
730
|
+
total += 1;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
return { total, memories: pageRows };
|
|
664
734
|
}
|
|
665
735
|
|
|
666
|
-
// No query: push lexical path safety into SQL, then count through the same
|
|
667
|
-
// realpath-aware row parser used for returned rows so symlink escapes do not
|
|
668
|
-
// inflate totals.
|
|
669
736
|
const browseWhereClauses = [...whereClauses, ...projectedBrowsePathSqlClauses()];
|
|
737
|
+
if (currentSelect.hasPathValid) {
|
|
738
|
+
browseWhereClauses.push("path_valid = 1");
|
|
739
|
+
}
|
|
670
740
|
const browseWhereSql = `WHERE ${browseWhereClauses.join(" AND ")}`;
|
|
741
|
+
|
|
742
|
+
if (currentSelect.hasPathValid) {
|
|
743
|
+
const rows = db
|
|
744
|
+
.prepare(`
|
|
745
|
+
SELECT
|
|
746
|
+
memory_id,
|
|
747
|
+
path_rel,
|
|
748
|
+
category,
|
|
749
|
+
status,
|
|
750
|
+
created_at,
|
|
751
|
+
updated_at,
|
|
752
|
+
entity_ref,
|
|
753
|
+
${currentSelect.tagsJson},
|
|
754
|
+
${currentSelect.previewText}
|
|
755
|
+
FROM memory_current
|
|
756
|
+
${browseWhereSql}
|
|
757
|
+
ORDER BY ${orderBySql}
|
|
758
|
+
`)
|
|
759
|
+
.iterate(...params) as Iterable<Record<string, unknown>>;
|
|
760
|
+
const pageRows: ProjectedMemoryBrowseRow[] = [];
|
|
761
|
+
let validRowsSeen = 0;
|
|
762
|
+
for (const row of rows) {
|
|
763
|
+
const browseRow = projectedBrowseRowFromCurrentRow(memoryDir, row, undefined, true);
|
|
764
|
+
if (!browseRow) continue;
|
|
765
|
+
if (validRowsSeen >= options.offset) {
|
|
766
|
+
pageRows.push(browseRow);
|
|
767
|
+
if (pageRows.length >= options.limit) break;
|
|
768
|
+
}
|
|
769
|
+
validRowsSeen += 1;
|
|
770
|
+
}
|
|
771
|
+
const totalRow = db
|
|
772
|
+
.prepare(`SELECT COUNT(*) AS total FROM memory_current ${browseWhereSql}`)
|
|
773
|
+
.get(...params) as { total: number };
|
|
774
|
+
return { total: totalRow.total, memories: pageRows };
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// Legacy projections have no materialized path validity. Preserve the
|
|
778
|
+
// realpath-aware full scan rather than trusting rows from an older writer.
|
|
671
779
|
const pageRows: ProjectedMemoryBrowseRow[] = [];
|
|
672
780
|
const fetchSize = Math.max(options.limit * 2, 50);
|
|
673
781
|
let validRowsSeen = 0;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { log } from "./logger.js";
|
|
2
|
+
|
|
3
|
+
const PROJECTION_FALLBACK_WARN_INTERVAL_MS = 5 * 60_000;
|
|
4
|
+
const projectionFallbackWarnedAt = new Map<string, number>();
|
|
5
|
+
|
|
6
|
+
export function assertMemoryFrontmatterId(
|
|
7
|
+
fm: { id: string; category?: string; created?: string; entityRef?: string },
|
|
8
|
+
): void {
|
|
9
|
+
if (typeof fm.id !== "string" || fm.id.trim().length === 0) {
|
|
10
|
+
const context = [
|
|
11
|
+
fm.category ? `category=${fm.category}` : null,
|
|
12
|
+
fm.created ? `created=${fm.created}` : null,
|
|
13
|
+
fm.entityRef ? `entityRef=${fm.entityRef}` : null,
|
|
14
|
+
].filter(Boolean).join(" ");
|
|
15
|
+
throw new Error(
|
|
16
|
+
`memory frontmatter id must not be blank${context ? ` (${context})` : ""}`,
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function warnProjectionFallback(memoryDir: string, consumer: string): null {
|
|
22
|
+
const key = `${memoryDir}\0${consumer}`;
|
|
23
|
+
const now = Date.now();
|
|
24
|
+
const warnedAt = projectionFallbackWarnedAt.get(key) ?? 0;
|
|
25
|
+
if (now - warnedAt >= PROJECTION_FALLBACK_WARN_INTERVAL_MS) {
|
|
26
|
+
projectionFallbackWarnedAt.set(key, now);
|
|
27
|
+
log.warn(`storage.${consumer}: memory projection absent or empty; falling back to full corpus`);
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
package/src/storage.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { createHash } from "node:crypto"
|
|
|
4
4
|
import { normalizeContent, computeContentHash } from "./content-hash.js";;
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { log } from "./logger.js";
|
|
7
|
+
import { assertMemoryFrontmatterId, warnProjectionFallback } from "./storage-guards.js";
|
|
7
8
|
import { MemoryReadStore } from "./storage/memory-read-store.js";
|
|
8
9
|
import {
|
|
9
10
|
readMaybeEncryptedLines,
|
|
@@ -102,11 +103,11 @@ import type {
|
|
|
102
103
|
import { confidenceTier, SPECULATIVE_TTL_DAYS } from "./types.js";
|
|
103
104
|
import {
|
|
104
105
|
type ProjectedMemoryBrowseOptions,
|
|
105
|
-
type ProjectedMemoryBrowsePage,
|
|
106
|
+
type ProjectedMemoryBrowsePage, markProjectedMemoryPathInvalid,
|
|
106
107
|
readProjectedMemoryState,
|
|
107
108
|
readProjectedMemoryBrowse,
|
|
108
109
|
readProjectedGovernanceRecord,
|
|
109
|
-
readProjectedMemoryTimeline,
|
|
110
|
+
readProjectedMemoryTimeline, updateProjectedMemoryPath,
|
|
110
111
|
} from "./memory-projection-store.js";
|
|
111
112
|
import {
|
|
112
113
|
inferMemoryStatus,
|
|
@@ -308,6 +309,7 @@ function stripCitationMarkersForHashRemoval(value: string, template: string): st
|
|
|
308
309
|
}
|
|
309
310
|
|
|
310
311
|
function serializeFrontmatter(fm: MemoryFrontmatter): string {
|
|
312
|
+
assertMemoryFrontmatterId(fm);
|
|
311
313
|
const lines = [
|
|
312
314
|
"---",
|
|
313
315
|
`id: ${fm.id}`,
|
|
@@ -4564,14 +4566,12 @@ export class StorageManager {
|
|
|
4564
4566
|
const dir = categoryDirName(memory.frontmatter.category);
|
|
4565
4567
|
return path.join(root, dir, this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
|
|
4566
4568
|
}
|
|
4567
|
-
|
|
4568
4569
|
private async writeMemoryFileAtomic(targetPath: string, memory: MemoryFile): Promise<void> {
|
|
4569
4570
|
const fileContent = `${serializeFrontmatter(memory.frontmatter)}\n\n${memory.content}\n`;
|
|
4570
4571
|
await writeMaybeEncryptedFile(targetPath, fileContent, this.resolveWriteKey(), {}, this.baseDir);
|
|
4571
4572
|
this.invalidateAllMemoriesCache();
|
|
4572
4573
|
this.notifyCatalogWrite();
|
|
4573
4574
|
}
|
|
4574
|
-
|
|
4575
4575
|
async moveMemoryToPath(memory: MemoryFile, targetPath: string): Promise<void> {
|
|
4576
4576
|
await this.writeMemoryFileAtomic(targetPath, memory);
|
|
4577
4577
|
const sourcePath = path.resolve(memory.path);
|
|
@@ -4589,6 +4589,7 @@ export class StorageManager {
|
|
|
4589
4589
|
// invalidated, but a concurrent readAllMemories() may have re-populated
|
|
4590
4590
|
// the cache between the write and the unlink.
|
|
4591
4591
|
this.invalidateAllMemoriesCache();
|
|
4592
|
+
updateProjectedMemoryPath(this.baseDir, memory.frontmatter.id, toMemoryPathRel(this.baseDir, targetPath));
|
|
4592
4593
|
}
|
|
4593
4594
|
}
|
|
4594
4595
|
|
|
@@ -4613,6 +4614,7 @@ export class StorageManager {
|
|
|
4613
4614
|
throw err;
|
|
4614
4615
|
}
|
|
4615
4616
|
}
|
|
4617
|
+
updateProjectedMemoryPath(this.baseDir, memory.frontmatter.id, toMemoryPathRel(this.baseDir, targetPath));
|
|
4616
4618
|
this.bumpMemoryStatusVersion();
|
|
4617
4619
|
return { changed: false, targetPath };
|
|
4618
4620
|
}
|
|
@@ -4660,7 +4662,7 @@ export class StorageManager {
|
|
|
4660
4662
|
|
|
4661
4663
|
// Write to archive location first (encrypted if applicable), then remove original.
|
|
4662
4664
|
await this.writeStorageSecureFile(destPath, fileContent);
|
|
4663
|
-
await unlink(memory.path);
|
|
4665
|
+
await unlink(memory.path); markProjectedMemoryPathInvalid(this.baseDir, memory.frontmatter.id);
|
|
4664
4666
|
this.invalidateAllMemoriesCache();
|
|
4665
4667
|
await this.appendGeneratedMemoryLifecycleEventFailOpen(
|
|
4666
4668
|
"storage.archiveMemory",
|
|
@@ -4765,7 +4767,7 @@ export class StorageManager {
|
|
|
4765
4767
|
if (!memory) return false;
|
|
4766
4768
|
|
|
4767
4769
|
try {
|
|
4768
|
-
await unlink(memory.path);
|
|
4770
|
+
await unlink(memory.path); markProjectedMemoryPathInvalid(this.baseDir, id);
|
|
4769
4771
|
this.invalidateAllMemoriesCache();
|
|
4770
4772
|
this.bumpMemoryStatusVersion();
|
|
4771
4773
|
log.debug(`invalidated memory ${id}`);
|
|
@@ -4904,7 +4906,7 @@ export class StorageManager {
|
|
|
4904
4906
|
const expiresAt = new Date(m.frontmatter.expiresAt).getTime();
|
|
4905
4907
|
if (expiresAt < now) {
|
|
4906
4908
|
try {
|
|
4907
|
-
await unlink(m.path);
|
|
4909
|
+
await unlink(m.path); markProjectedMemoryPathInvalid(this.baseDir, m.frontmatter.id);
|
|
4908
4910
|
deleted.push(m);
|
|
4909
4911
|
log.debug(`cleaned expired memory ${m.frontmatter.id} (TTL expired)`);
|
|
4910
4912
|
} catch {
|
|
@@ -5979,7 +5981,7 @@ export class StorageManager {
|
|
|
5979
5981
|
if (updatedAt < cutoff) {
|
|
5980
5982
|
// Remove the file
|
|
5981
5983
|
try {
|
|
5982
|
-
await unlink(m.path);
|
|
5984
|
+
await unlink(m.path); markProjectedMemoryPathInvalid(this.baseDir, m.frontmatter.id);
|
|
5983
5985
|
deleted.push(m);
|
|
5984
5986
|
log.debug(`cleaned expired commitment ${m.frontmatter.id}`);
|
|
5985
5987
|
} catch {
|
|
@@ -6020,8 +6022,8 @@ export class StorageManager {
|
|
|
6020
6022
|
lastAccessed: entry.lastAccessed,
|
|
6021
6023
|
};
|
|
6022
6024
|
|
|
6023
|
-
const fileContent = `${serializeFrontmatter(newFm)}\n\n${memory.content}\n`;
|
|
6024
6025
|
try {
|
|
6026
|
+
const fileContent = `${serializeFrontmatter(newFm)}\n\n${memory.content}\n`;
|
|
6025
6027
|
await this.writeStorageSecureFile(memory.path, fileContent);
|
|
6026
6028
|
updated++;
|
|
6027
6029
|
} catch (err) {
|
|
@@ -6070,8 +6072,7 @@ export class StorageManager {
|
|
|
6070
6072
|
|
|
6071
6073
|
async getProjectedMemoryState(id: string): Promise<MemoryProjectionCurrentState | null> {
|
|
6072
6074
|
const projected = readProjectedMemoryState(this.baseDir, id);
|
|
6073
|
-
if (projected) return projected;
|
|
6074
|
-
|
|
6075
|
+
if (projected) return projected; warnProjectionFallback(this.baseDir, "getProjectedMemoryState");
|
|
6075
6076
|
const active = await this.getMemoryById(id);
|
|
6076
6077
|
if (active) return this.toProjectedCurrentState(active, "active");
|
|
6077
6078
|
|
|
@@ -6084,7 +6085,8 @@ export class StorageManager {
|
|
|
6084
6085
|
async browseProjectedMemories(
|
|
6085
6086
|
options: ProjectedMemoryBrowseOptions,
|
|
6086
6087
|
): Promise<ProjectedMemoryBrowsePage | null> {
|
|
6087
|
-
return readProjectedMemoryBrowse(this.baseDir, options)
|
|
6088
|
+
return readProjectedMemoryBrowse(this.baseDir, options)
|
|
6089
|
+
?? warnProjectionFallback(this.baseDir, "browseProjectedMemories");
|
|
6088
6090
|
}
|
|
6089
6091
|
|
|
6090
6092
|
async getProjectedGovernanceRecord(): Promise<ReturnType<typeof readProjectedGovernanceRecord>> {
|
|
@@ -6124,8 +6126,7 @@ export class StorageManager {
|
|
|
6124
6126
|
if (cappedLimit === 0) return [];
|
|
6125
6127
|
|
|
6126
6128
|
const projected = readProjectedMemoryTimeline(this.baseDir, memoryId, cappedLimit);
|
|
6127
|
-
if (projected && projected.length > 0) return projected;
|
|
6128
|
-
|
|
6129
|
+
if (projected && projected.length > 0) return projected; warnProjectionFallback(this.baseDir, "getMemoryTimeline");
|
|
6129
6130
|
const events = await this.readAllMemoryLifecycleEvents();
|
|
6130
6131
|
return events.filter((event) => event.memoryId === memoryId).slice(-cappedLimit);
|
|
6131
6132
|
}
|
|
@@ -6382,9 +6383,8 @@ export class StorageManager {
|
|
|
6382
6383
|
updated: now,
|
|
6383
6384
|
};
|
|
6384
6385
|
|
|
6385
|
-
const fileContent = `${serializeFrontmatter(updatedFm)}\n\n${memory.content}\n`;
|
|
6386
|
-
|
|
6387
6386
|
try {
|
|
6387
|
+
const fileContent = `${serializeFrontmatter(updatedFm)}\n\n${memory.content}\n`;
|
|
6388
6388
|
await this.writeStorageSecureFile(memory.path, fileContent);
|
|
6389
6389
|
await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.archiveMemories", {
|
|
6390
6390
|
memoryId: id,
|