@remnic/core 9.38.2 → 9.39.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.
- package/dist/access-admin-ops-surface.d.ts +5 -5
- package/dist/access-admin-ops-surface.js +13 -13
- package/dist/access-authorization-probe.d.ts +5 -5
- package/dist/access-authorization-probe.js +14 -14
- package/dist/access-boundary.d.ts +5 -5
- package/dist/access-boundary.js +13 -13
- package/dist/access-cli.js +35 -35
- package/dist/access-extraction-force-flush.d.ts +5 -5
- package/dist/access-extraction-force-flush.js +13 -13
- package/dist/access-http-lcm-compaction.d.ts +5 -5
- package/dist/access-http-lifecycle-flush.d.ts +5 -5
- package/dist/access-http.d.ts +5 -5
- package/dist/access-http.js +18 -18
- package/dist/access-identity-continuity-surface.d.ts +4 -4
- package/dist/access-identity-continuity-surface.js +13 -13
- package/dist/access-lcm-surface.d.ts +5 -5
- package/dist/access-lcm-surface.js +13 -13
- package/dist/access-mcp.d.ts +5 -5
- package/dist/access-mcp.js +16 -16
- package/dist/access-namespace-preflight.js +13 -13
- package/dist/access-observe-write-surface.d.ts +5 -5
- package/dist/access-observe-write-surface.js +13 -13
- package/dist/access-operations-batch.js +14 -14
- package/dist/access-operations.d.ts +5 -5
- package/dist/access-operations.js +15 -15
- package/dist/access-recall-concurrency.d.ts +5 -5
- package/dist/access-recall-concurrency.js +13 -13
- package/dist/access-recall-response.d.ts +5 -5
- package/dist/access-recall-response.js +13 -13
- package/dist/access-recall-surface.d.ts +5 -5
- package/dist/access-recall-surface.js +13 -13
- package/dist/{access-service-C2WYT1pO.d.ts → access-service-C6QuKBCG.d.ts} +3 -3
- package/dist/access-service-helpers.d.ts +5 -5
- package/dist/access-service.d.ts +5 -5
- package/dist/access-service.js +13 -13
- package/dist/access-surface-catalog.d.ts +5 -5
- package/dist/access-wearables-meetings-surface.d.ts +2 -2
- package/dist/bootstrap.d.ts +4 -4
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +5 -5
- package/dist/buffer.d.ts +1 -1
- package/dist/buffer.js +2 -2
- package/dist/causal-consolidation.js +6 -6
- package/dist/{chunk-2YJPCRUM.js → chunk-4CSTEPFA.js} +2 -2
- package/dist/{chunk-T3FVAHM7.js → chunk-4VXGRIH6.js} +286 -7
- package/dist/chunk-4VXGRIH6.js.map +1 -0
- package/dist/{chunk-A4P6H25D.js → chunk-5JUYBWJD.js} +19 -19
- package/dist/{chunk-JYLN6KHH.js → chunk-5TRIAPT4.js} +7 -9
- package/dist/{chunk-JYLN6KHH.js.map → chunk-5TRIAPT4.js.map} +1 -1
- package/dist/{chunk-6D5D5A4M.js → chunk-7ERHAARG.js} +2 -2
- package/dist/{chunk-GORHSN5C.js → chunk-7OCTH7AJ.js} +2 -2
- package/dist/{chunk-ILGC6JH6.js → chunk-7RJUVTVF.js} +16 -1
- package/dist/chunk-7RJUVTVF.js.map +1 -0
- package/dist/{chunk-FE7MUX7M.js → chunk-AL73FWSS.js} +2 -2
- package/dist/{chunk-6TF4M2LB.js → chunk-AVGTOOYP.js} +2 -2
- package/dist/{chunk-B6XHIBIB.js → chunk-AX2J2LC4.js} +2 -2
- package/dist/{chunk-ZB3YODID.js → chunk-BVJIAIB3.js} +8 -8
- package/dist/{chunk-XN3EW6P2.js → chunk-CMENJQ6I.js} +2 -2
- package/dist/{chunk-PRP3M5Y3.js → chunk-CMFUYOYB.js} +2 -2
- package/dist/{chunk-M3DBB2ND.js → chunk-CNZLKQEL.js} +2 -2
- package/dist/{chunk-YVMVVE4B.js → chunk-CPD6C6K6.js} +2 -2
- package/dist/{chunk-VU7732DU.js → chunk-CYM2PHLB.js} +5 -5
- package/dist/{chunk-RY2XZTFN.js → chunk-DJH35IVS.js} +2 -2
- package/dist/{chunk-Q3CVY7LV.js → chunk-ENLZOWYK.js} +201 -231
- package/dist/chunk-ENLZOWYK.js.map +1 -0
- package/dist/{chunk-IJAQCBGV.js → chunk-F5EVTFQK.js} +2 -2
- package/dist/{chunk-RY7CHVGE.js → chunk-G57BGHQ2.js} +2 -2
- package/dist/{chunk-F3BNEYX2.js → chunk-GUQYMOPU.js} +2 -2
- package/dist/{chunk-IMEXE4XS.js → chunk-GYGXKATH.js} +6 -6
- package/dist/{chunk-CKBKHZKZ.js → chunk-JKTHT4KX.js} +3 -3
- package/dist/{chunk-HQYYJRU7.js → chunk-KJP2R5MJ.js} +15 -15
- package/dist/{chunk-UBWOCQ3I.js → chunk-MHCRASVK.js} +2 -2
- package/dist/{chunk-RYMPLSIT.js → chunk-NF3U3MPG.js} +41 -8
- package/dist/chunk-NF3U3MPG.js.map +1 -0
- package/dist/{chunk-DKKZ6SO2.js → chunk-SHTPVI47.js} +22 -45
- package/dist/chunk-SHTPVI47.js.map +1 -0
- package/dist/{chunk-PYTUBWAS.js → chunk-UGAZILZG.js} +2 -2
- package/dist/{chunk-4TAD3B56.js → chunk-VGK33D3K.js} +2 -2
- package/dist/{chunk-BGLDKAYY.js → chunk-VK33JSBR.js} +2 -2
- package/dist/{chunk-NBAEGFXT.js → chunk-VQTRSX4R.js} +11 -14
- package/dist/chunk-VQTRSX4R.js.map +1 -0
- package/dist/{chunk-WET4M3R6.js → chunk-XSPD6KQL.js} +2 -2
- package/dist/{chunk-HUS2XN3T.js → chunk-YFHMW37V.js} +2 -2
- package/dist/{chunk-HKF5VASQ.js → chunk-ZA53AZND.js} +17 -6
- package/dist/chunk-ZA53AZND.js.map +1 -0
- package/dist/{chunk-5ZGHUWTT.js → chunk-ZBDORYWM.js} +2 -2
- package/dist/{cli-jkmzdT8P.d.ts → cli-ClXRCu08.d.ts} +3 -3
- package/dist/cli.d.ts +6 -6
- package/dist/cli.js +27 -27
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +5 -5
- package/dist/connectors/codex-materialize-runner.js +5 -5
- package/dist/connectors/index.js +5 -5
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/contradiction/index.d.ts +1 -1
- package/dist/corpus-watermark.js +7 -7
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +5 -5
- package/dist/explicit-capture.d.ts +4 -4
- package/dist/extraction.js +8 -8
- package/dist/index.d.ts +10 -10
- package/dist/index.js +35 -35
- package/dist/maintenance/memory-governance.js +5 -5
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +1 -1
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +5 -5
- package/dist/maintenance/rebuild-memory-projection.d.ts +17 -10
- package/dist/maintenance/rebuild-memory-projection.js +6 -6
- package/dist/{maintenance-Ha9Dg93M.d.ts → maintenance-Dsvprbod.d.ts} +1 -1
- package/dist/mcp-memory-inspector-app.d.ts +5 -5
- package/dist/memory-lifecycle-ledger-utils.d.ts +8 -1
- package/dist/memory-lifecycle-ledger-utils.js +5 -1
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +1 -1
- package/dist/namespaces/migrate.js +6 -6
- package/dist/namespaces/storage.d.ts +1 -1
- package/dist/namespaces/storage.js +5 -5
- package/dist/offline-sync-impression-drain.js +5 -3
- package/dist/operator-doctor-corpus.js +8 -8
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +11 -11
- package/dist/orchestration/compression-guideline-coordinator.d.ts +1 -1
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +10 -10
- package/dist/{orchestrator-DV_u9bfx.d.ts → orchestrator-CEhod0MJ.d.ts} +3 -3
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +35 -35
- package/dist/semantic-consolidation.js +6 -6
- package/dist/semantic-rule-promotion.js +5 -5
- package/dist/semantic-rule-verifier.js +5 -5
- package/dist/{service-JxXXY-Eh.d.ts → service-BlOfGid8.d.ts} +1 -1
- package/dist/source-agent-qualifier.js +7 -7
- package/dist/{storage-BbH4HDUS.d.ts → storage-CNKZocJF.d.ts} +1 -1
- package/dist/storage-guards.d.ts +9 -2
- package/dist/storage-guards.js +3 -1
- package/dist/storage.d.ts +1 -1
- package/dist/storage.js +4 -4
- package/dist/temporal-supersession.d.ts +1 -1
- package/dist/tier-migration.d.ts +1 -1
- package/dist/verified-recall.js +5 -5
- package/package.json +2 -2
- package/src/maintenance/projection-support.ts +192 -1
- package/src/maintenance/rebuild-memory-lifecycle-ledger.ts +5 -16
- package/src/maintenance/rebuild-memory-projection.ts +28 -60
- package/src/memory-lifecycle-ledger-utils.ts +21 -0
- package/src/orchestration/extraction-run-helpers.ts +47 -0
- package/src/orchestration/extraction-run.test.ts +133 -0
- package/src/orchestration/extraction-run.ts +11 -7
- package/src/orchestration/maintenance.test.ts +30 -0
- package/src/orchestrator-flush.test.ts +51 -0
- package/src/storage/memory-lifecycle-ledger-access.ts +198 -1
- package/src/storage/secure-line-reader.ts +3 -1
- package/src/storage-guards.ts +28 -7
- package/src/storage-memory-lifecycle-ledger.test.ts +291 -1
- package/src/storage.ts +11 -21
- package/dist/chunk-DKKZ6SO2.js.map +0 -1
- package/dist/chunk-HKF5VASQ.js.map +0 -1
- package/dist/chunk-ILGC6JH6.js.map +0 -1
- package/dist/chunk-NBAEGFXT.js.map +0 -1
- package/dist/chunk-Q3CVY7LV.js.map +0 -1
- package/dist/chunk-RYMPLSIT.js.map +0 -1
- package/dist/chunk-T3FVAHM7.js.map +0 -1
- /package/dist/{chunk-2YJPCRUM.js.map → chunk-4CSTEPFA.js.map} +0 -0
- /package/dist/{chunk-A4P6H25D.js.map → chunk-5JUYBWJD.js.map} +0 -0
- /package/dist/{chunk-6D5D5A4M.js.map → chunk-7ERHAARG.js.map} +0 -0
- /package/dist/{chunk-GORHSN5C.js.map → chunk-7OCTH7AJ.js.map} +0 -0
- /package/dist/{chunk-FE7MUX7M.js.map → chunk-AL73FWSS.js.map} +0 -0
- /package/dist/{chunk-6TF4M2LB.js.map → chunk-AVGTOOYP.js.map} +0 -0
- /package/dist/{chunk-B6XHIBIB.js.map → chunk-AX2J2LC4.js.map} +0 -0
- /package/dist/{chunk-ZB3YODID.js.map → chunk-BVJIAIB3.js.map} +0 -0
- /package/dist/{chunk-XN3EW6P2.js.map → chunk-CMENJQ6I.js.map} +0 -0
- /package/dist/{chunk-PRP3M5Y3.js.map → chunk-CMFUYOYB.js.map} +0 -0
- /package/dist/{chunk-M3DBB2ND.js.map → chunk-CNZLKQEL.js.map} +0 -0
- /package/dist/{chunk-YVMVVE4B.js.map → chunk-CPD6C6K6.js.map} +0 -0
- /package/dist/{chunk-VU7732DU.js.map → chunk-CYM2PHLB.js.map} +0 -0
- /package/dist/{chunk-RY2XZTFN.js.map → chunk-DJH35IVS.js.map} +0 -0
- /package/dist/{chunk-IJAQCBGV.js.map → chunk-F5EVTFQK.js.map} +0 -0
- /package/dist/{chunk-RY7CHVGE.js.map → chunk-G57BGHQ2.js.map} +0 -0
- /package/dist/{chunk-F3BNEYX2.js.map → chunk-GUQYMOPU.js.map} +0 -0
- /package/dist/{chunk-IMEXE4XS.js.map → chunk-GYGXKATH.js.map} +0 -0
- /package/dist/{chunk-CKBKHZKZ.js.map → chunk-JKTHT4KX.js.map} +0 -0
- /package/dist/{chunk-HQYYJRU7.js.map → chunk-KJP2R5MJ.js.map} +0 -0
- /package/dist/{chunk-UBWOCQ3I.js.map → chunk-MHCRASVK.js.map} +0 -0
- /package/dist/{chunk-PYTUBWAS.js.map → chunk-UGAZILZG.js.map} +0 -0
- /package/dist/{chunk-4TAD3B56.js.map → chunk-VGK33D3K.js.map} +0 -0
- /package/dist/{chunk-BGLDKAYY.js.map → chunk-VK33JSBR.js.map} +0 -0
- /package/dist/{chunk-WET4M3R6.js.map → chunk-XSPD6KQL.js.map} +0 -0
- /package/dist/{chunk-HUS2XN3T.js.map → chunk-YFHMW37V.js.map} +0 -0
- /package/dist/{chunk-5ZGHUWTT.js.map → chunk-ZBDORYWM.js.map} +0 -0
|
@@ -21,6 +21,7 @@ import path from "node:path";
|
|
|
21
21
|
import { readProjectionRebuiltAt } from "../maintenance/projection-support.js";
|
|
22
22
|
import { lstat, mkdir, mkdtemp, readdir, readFile, rm, stat, symlink, writeFile } from "node:fs/promises";
|
|
23
23
|
import { execFileSync } from "node:child_process";
|
|
24
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
24
25
|
|
|
25
26
|
import { MaintenanceScheduler } from "./maintenance.js";
|
|
26
27
|
import {
|
|
@@ -2952,3 +2953,32 @@ test("scheduled projection rebuild skips when the projection was rebuilt within
|
|
|
2952
2953
|
await rm(memoryDir, { recursive: true, force: true });
|
|
2953
2954
|
}
|
|
2954
2955
|
});
|
|
2956
|
+
|
|
2957
|
+
test("maintenance tool request invokes the scheduled projection rebuild wiring (#2119)", async () => {
|
|
2958
|
+
const memoryDir = await seedMemoryDirWithOneFact();
|
|
2959
|
+
const scheduler = new MaintenanceScheduler({
|
|
2960
|
+
config: fixtureConfig({
|
|
2961
|
+
memoryDir,
|
|
2962
|
+
projectionRebuildEnabled: true,
|
|
2963
|
+
projectionRebuildIntervalMs: 6 * 60 * 60 * 1000,
|
|
2964
|
+
}),
|
|
2965
|
+
getQmd: () => ({ isAvailable: () => false }) as unknown as SearchBackend,
|
|
2966
|
+
namespaceSearchRouter: {} as unknown as NamespaceSearchRouter,
|
|
2967
|
+
namespaceCatalog: {} as unknown as NamespaceCatalog,
|
|
2968
|
+
});
|
|
2969
|
+
try {
|
|
2970
|
+
scheduler.requestQmdMaintenanceForTool("test");
|
|
2971
|
+
for (let attempt = 0; attempt < 100 && probeProjectionHealth(memoryDir).state === "absent"; attempt += 1) {
|
|
2972
|
+
await delay(10);
|
|
2973
|
+
}
|
|
2974
|
+
assert.equal(
|
|
2975
|
+
probeProjectionHealth(memoryDir).state,
|
|
2976
|
+
"openable",
|
|
2977
|
+
"the public maintenance request entrypoint must reach the projection rebuild",
|
|
2978
|
+
);
|
|
2979
|
+
assert.equal(readProjectedMemoryBrowse(memoryDir, { limit: 5, offset: 0 })?.total, 1);
|
|
2980
|
+
} finally {
|
|
2981
|
+
await scheduler.dispose();
|
|
2982
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
2983
|
+
}
|
|
2984
|
+
});
|
|
@@ -1781,6 +1781,57 @@ test("runExtraction preserves empty-result buffers when fingerprint persistence
|
|
|
1781
1781
|
assert.equal(clearCalls, 0);
|
|
1782
1782
|
});
|
|
1783
1783
|
|
|
1784
|
+
test("runExtraction keeps the empty live path fail-open when liveness metadata write fails (#2223)", async () => {
|
|
1785
|
+
const config = parseConfig({});
|
|
1786
|
+
config.extractionMinChars = 0;
|
|
1787
|
+
config.extractionMinUserTurns = 1;
|
|
1788
|
+
|
|
1789
|
+
let clearCalls = 0;
|
|
1790
|
+
let saveMetaCalls = 0;
|
|
1791
|
+
const meta = {
|
|
1792
|
+
extractionCount: 0,
|
|
1793
|
+
lastExtractionAt: null,
|
|
1794
|
+
lastConsolidationAt: null,
|
|
1795
|
+
totalMemories: 0,
|
|
1796
|
+
totalEntities: 0,
|
|
1797
|
+
processedExtractionFingerprints: [] as Array<{ fingerprint: string; observedAt: string }>,
|
|
1798
|
+
};
|
|
1799
|
+
|
|
1800
|
+
const orchestrator = Object.create(Orchestrator.prototype) as any;
|
|
1801
|
+
orchestrator.config = config;
|
|
1802
|
+
orchestrator.buffer = {
|
|
1803
|
+
clearAfterExtraction: async () => {
|
|
1804
|
+
clearCalls += 1;
|
|
1805
|
+
},
|
|
1806
|
+
};
|
|
1807
|
+
orchestrator.storageRouter = {
|
|
1808
|
+
storageFor: async () => ({
|
|
1809
|
+
listEntityNames: async () => [],
|
|
1810
|
+
loadMeta: async () => meta,
|
|
1811
|
+
saveMeta: async () => {
|
|
1812
|
+
saveMetaCalls += 1;
|
|
1813
|
+
throw new Error("meta save failed");
|
|
1814
|
+
},
|
|
1815
|
+
}),
|
|
1816
|
+
};
|
|
1817
|
+
orchestrator.extraction = {
|
|
1818
|
+
extract: async () => ({ facts: [], entities: [], questions: [], profileUpdates: [] }),
|
|
1819
|
+
};
|
|
1820
|
+
|
|
1821
|
+
// Normal live turn (no persistProcessedFingerprint): the liveness stamp is
|
|
1822
|
+
// diagnostic only, so a metadata write failure must neither reject the task
|
|
1823
|
+
// nor retain the buffer — extraction succeeded and produced nothing durable.
|
|
1824
|
+
const result = await orchestrator.runExtraction(
|
|
1825
|
+
[makeTurn("session-live-empty", "a transient note not worth remembering")],
|
|
1826
|
+
{ bufferKey: "session-live-empty" },
|
|
1827
|
+
);
|
|
1828
|
+
|
|
1829
|
+
assert.equal(result.status, "skipped");
|
|
1830
|
+
assert.equal(result.reason, "empty_extraction_result");
|
|
1831
|
+
assert.equal(saveMetaCalls, 1, "the liveness stamp was attempted");
|
|
1832
|
+
assert.equal(clearCalls, 1, "fail-open: the live buffer clears despite the metadata write failure");
|
|
1833
|
+
});
|
|
1834
|
+
|
|
1784
1835
|
test("runExtraction preserves deduped buffers when the caller aborts during meta load", async () => {
|
|
1785
1836
|
const config = parseConfig({});
|
|
1786
1837
|
config.extractionMinChars = 0;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SecureStoreLockedError } from "../secure-store/secure-fs.js";
|
|
2
2
|
import { isErrnoCode } from "../utils/errno.js";
|
|
3
3
|
import { displayErrorDetail } from "../runtime/better-sqlite.js";
|
|
4
|
-
import { rename, stat, unlink } from "node:fs/promises";
|
|
4
|
+
import { lstat, rename, stat, unlink } from "node:fs/promises";
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import type { MemoryLifecycleEvent } from "../types.js";
|
|
@@ -19,6 +19,15 @@ import {
|
|
|
19
19
|
readMemoryLifecycleEventsFromLines,
|
|
20
20
|
STATE_FILE_MAX_DECRYPT_BYTES,
|
|
21
21
|
} from "./secure-line-reader.js";
|
|
22
|
+
import { getMemoryProjectionPath } from "../memory-projection-store.js";
|
|
23
|
+
import {
|
|
24
|
+
openProjectionLifecycleLedgerLagTracker,
|
|
25
|
+
type ProjectionLifecycleLedgerLagTracker,
|
|
26
|
+
} from "../maintenance/projection-support.js";
|
|
27
|
+
import {
|
|
28
|
+
type ProjectionLedgerLagTelemetry,
|
|
29
|
+
warnProjectionFallback,
|
|
30
|
+
} from "../storage-guards.js";
|
|
22
31
|
|
|
23
32
|
/** Reads a lifecycle-ledger file through the secure whole-file/plaintext line source. */
|
|
24
33
|
export type LedgerSecureReader = (filePath: string) => Promise<string>;
|
|
@@ -190,6 +199,194 @@ export async function readBoundedLifecycleEventsFromLedger(
|
|
|
190
199
|
}
|
|
191
200
|
}
|
|
192
201
|
|
|
202
|
+
export interface LifecycleLedgerProjectionLagRead {
|
|
203
|
+
events: MemoryLifecycleEvent[];
|
|
204
|
+
currentEventCount: number;
|
|
205
|
+
deltaEvents: number;
|
|
206
|
+
telemetryAvailable: boolean;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export async function readBoundedLifecycleEventsWithProjectionLag(
|
|
210
|
+
ledgerPath: string,
|
|
211
|
+
readSecureFile: LedgerSecureReader,
|
|
212
|
+
limit: number,
|
|
213
|
+
memoryId: string,
|
|
214
|
+
tracker: ProjectionLifecycleLedgerLagTracker,
|
|
215
|
+
): Promise<LifecycleLedgerProjectionLagRead> {
|
|
216
|
+
let currentEventCount = 0;
|
|
217
|
+
let deltaEvents = 0;
|
|
218
|
+
let telemetryAvailable = true;
|
|
219
|
+
try {
|
|
220
|
+
const events = await readMemoryLifecycleEventsFromLines(
|
|
221
|
+
readMaybeEncryptedLines(
|
|
222
|
+
ledgerPath,
|
|
223
|
+
() => readSecureFile(ledgerPath),
|
|
224
|
+
STATE_FILE_MAX_DECRYPT_BYTES,
|
|
225
|
+
),
|
|
226
|
+
Math.max(0, Math.floor(limit)),
|
|
227
|
+
memoryId,
|
|
228
|
+
compareMemoryLifecycleEvents,
|
|
229
|
+
(event) => {
|
|
230
|
+
if (!telemetryAvailable) return;
|
|
231
|
+
try {
|
|
232
|
+
const tracked = tracker.record(event);
|
|
233
|
+
if (!tracked.unique) return;
|
|
234
|
+
currentEventCount += 1;
|
|
235
|
+
if (!tracked.projected) deltaEvents += 1;
|
|
236
|
+
} catch {
|
|
237
|
+
telemetryAvailable = false;
|
|
238
|
+
currentEventCount = 0;
|
|
239
|
+
deltaEvents = 0;
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
);
|
|
243
|
+
return { events, currentEventCount, deltaEvents, telemetryAvailable };
|
|
244
|
+
} catch (err) {
|
|
245
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
246
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
247
|
+
return { events: [], currentEventCount: 0, deltaEvents: 0, telemetryAvailable: false };
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Probe filesystem identity stamps (dev:ino:size:mtimeMs:ctimeMs) for the
|
|
252
|
+
* projection database and lifecycle-ledger file. Returns `null` when either
|
|
253
|
+
* file is missing or unstatable so caller can disable the lag cache without
|
|
254
|
+
* throwing on transient I/O or permission errors (#2119 / review).
|
|
255
|
+
*/
|
|
256
|
+
export async function probeProjectionLedgerIdentities(
|
|
257
|
+
projectionPath: string,
|
|
258
|
+
ledgerPath: string,
|
|
259
|
+
): Promise<{ projectionIdentity: string; ledgerIdentity: string } | null> {
|
|
260
|
+
try {
|
|
261
|
+
const [projectionIdentity, ledgerIdentity] = await Promise.all(
|
|
262
|
+
[projectionPath, ledgerPath].map(async (filePath) => {
|
|
263
|
+
try {
|
|
264
|
+
const file = await lstat(filePath);
|
|
265
|
+
return `${file.dev}:${file.ino}:${file.size}:${file.mtimeMs}:${file.ctimeMs}`;
|
|
266
|
+
} catch (err) {
|
|
267
|
+
if (isErrnoCode(err, "ENOENT")) return "absent";
|
|
268
|
+
// Probe failure: disable lag cache, do not block the primary timeline read (#2119 review).
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
}),
|
|
272
|
+
);
|
|
273
|
+
if (!projectionIdentity || !ledgerIdentity) return null;
|
|
274
|
+
return { projectionIdentity, ledgerIdentity };
|
|
275
|
+
} catch {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export interface ProjectionLedgerLagScan {
|
|
281
|
+
telemetry: ProjectionLedgerLagTelemetry | null;
|
|
282
|
+
events: MemoryLifecycleEvent[];
|
|
283
|
+
memoryId: string;
|
|
284
|
+
limit: number;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export class ProjectionLedgerLagManager {
|
|
288
|
+
private lagCache: {
|
|
289
|
+
generation: string;
|
|
290
|
+
telemetry: ProjectionLedgerLagTelemetry;
|
|
291
|
+
} | null = null;
|
|
292
|
+
private lagInFlight: {
|
|
293
|
+
generation: string;
|
|
294
|
+
scan: Promise<ProjectionLedgerLagScan>;
|
|
295
|
+
} | null = null;
|
|
296
|
+
|
|
297
|
+
async resolveTimelineWithLag(options: {
|
|
298
|
+
baseDir: string;
|
|
299
|
+
ledgerPath: string;
|
|
300
|
+
memoryId: string;
|
|
301
|
+
cappedLimit: number;
|
|
302
|
+
readSecureFile: LedgerSecureReader;
|
|
303
|
+
projectionAge: () => string;
|
|
304
|
+
}): Promise<MemoryLifecycleEvent[]> {
|
|
305
|
+
const { baseDir, ledgerPath, memoryId, cappedLimit, readSecureFile, projectionAge } = options;
|
|
306
|
+
const projectionPath = getMemoryProjectionPath(baseDir);
|
|
307
|
+
const identities = await probeProjectionLedgerIdentities(projectionPath, ledgerPath);
|
|
308
|
+
|
|
309
|
+
if (!identities) {
|
|
310
|
+
warnProjectionFallback(baseDir, "getMemoryTimeline", projectionAge);
|
|
311
|
+
return readBoundedLifecycleEventsFromLedger(ledgerPath, readSecureFile, cappedLimit, memoryId);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const { projectionIdentity, ledgerIdentity } = identities;
|
|
315
|
+
const generation = `${projectionIdentity}\0${ledgerIdentity}`;
|
|
316
|
+
|
|
317
|
+
const cachedLag = this.lagCache?.generation === generation
|
|
318
|
+
? this.lagCache.telemetry
|
|
319
|
+
: null;
|
|
320
|
+
if (cachedLag) {
|
|
321
|
+
warnProjectionFallback(baseDir, "getMemoryTimeline", projectionAge, cachedLag);
|
|
322
|
+
return readBoundedLifecycleEventsFromLedger(ledgerPath, readSecureFile, cappedLimit, memoryId);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const inFlight = this.lagInFlight?.generation === generation
|
|
326
|
+
? this.lagInFlight.scan
|
|
327
|
+
: null;
|
|
328
|
+
if (inFlight) {
|
|
329
|
+
const result = await inFlight;
|
|
330
|
+
if (result.telemetry) {
|
|
331
|
+
this.lagCache = { generation, telemetry: result.telemetry };
|
|
332
|
+
}
|
|
333
|
+
warnProjectionFallback(baseDir, "getMemoryTimeline", projectionAge, result.telemetry ?? undefined);
|
|
334
|
+
if (result.memoryId === memoryId && result.limit === cappedLimit) return result.events;
|
|
335
|
+
return readBoundedLifecycleEventsFromLedger(ledgerPath, readSecureFile, cappedLimit, memoryId);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const tracker = openProjectionLifecycleLedgerLagTracker(baseDir);
|
|
339
|
+
if (tracker) {
|
|
340
|
+
const highWater = tracker.highWater;
|
|
341
|
+
const scan = (async (): Promise<ProjectionLedgerLagScan> => {
|
|
342
|
+
try {
|
|
343
|
+
const fallback = await readBoundedLifecycleEventsWithProjectionLag(
|
|
344
|
+
ledgerPath,
|
|
345
|
+
readSecureFile,
|
|
346
|
+
cappedLimit,
|
|
347
|
+
memoryId,
|
|
348
|
+
tracker,
|
|
349
|
+
);
|
|
350
|
+
return {
|
|
351
|
+
telemetry: fallback.telemetryAvailable
|
|
352
|
+
? {
|
|
353
|
+
projectedEvents: highWater.eventCount,
|
|
354
|
+
currentLedgerEvents: fallback.currentEventCount,
|
|
355
|
+
deltaEvents: fallback.deltaEvents,
|
|
356
|
+
}
|
|
357
|
+
: null,
|
|
358
|
+
events: fallback.events,
|
|
359
|
+
memoryId,
|
|
360
|
+
limit: cappedLimit,
|
|
361
|
+
};
|
|
362
|
+
} finally {
|
|
363
|
+
try {
|
|
364
|
+
tracker.close();
|
|
365
|
+
} catch {
|
|
366
|
+
// Lag telemetry cleanup must not replace a successful ledger fallback.
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
})();
|
|
370
|
+
this.lagInFlight = { generation, scan };
|
|
371
|
+
try {
|
|
372
|
+
const result = await scan;
|
|
373
|
+
if (result.telemetry) {
|
|
374
|
+
this.lagCache = { generation, telemetry: result.telemetry };
|
|
375
|
+
}
|
|
376
|
+
warnProjectionFallback(baseDir, "getMemoryTimeline", projectionAge, result.telemetry ?? undefined);
|
|
377
|
+
return result.events;
|
|
378
|
+
} finally {
|
|
379
|
+
if (this.lagInFlight?.scan === scan) {
|
|
380
|
+
this.lagInFlight = null;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
warnProjectionFallback(baseDir, "getMemoryTimeline", projectionAge);
|
|
386
|
+
return readBoundedLifecycleEventsFromLedger(ledgerPath, readSecureFile, cappedLimit, memoryId);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
193
390
|
/**
|
|
194
391
|
* Directory holding the durable pending-append spill for a lifecycle ledger
|
|
195
392
|
* (#2033). When an append cannot acquire the ledger lock within its budget —
|
|
@@ -227,6 +227,7 @@ export async function readMemoryLifecycleEventsFromLines(
|
|
|
227
227
|
limit: number,
|
|
228
228
|
memoryId?: string,
|
|
229
229
|
compare?: (a: MemoryLifecycleEvent, b: MemoryLifecycleEvent) => number,
|
|
230
|
+
onEvent?: (event: MemoryLifecycleEvent) => void,
|
|
230
231
|
): Promise<MemoryLifecycleEvent[]> {
|
|
231
232
|
if (limit <= 0) return [];
|
|
232
233
|
const top = compare ? new BoundedLifecycleTopN(limit, compare) : null;
|
|
@@ -238,8 +239,9 @@ export async function readMemoryLifecycleEventsFromLines(
|
|
|
238
239
|
if (!line) continue;
|
|
239
240
|
try {
|
|
240
241
|
const parsed = JSON.parse(line) as Partial<MemoryLifecycleEvent>;
|
|
241
|
-
if (memoryId !== undefined && parsed.memoryId !== memoryId) continue;
|
|
242
242
|
if (!isValidLifecycleEventRow(parsed)) continue;
|
|
243
|
+
onEvent?.(parsed);
|
|
244
|
+
if (memoryId !== undefined && parsed.memoryId !== memoryId) continue;
|
|
243
245
|
if (top) {
|
|
244
246
|
top.add(parsed, seq++);
|
|
245
247
|
} else if (ring.length < limit) {
|
package/src/storage-guards.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { log } from "./logger.js";
|
|
2
2
|
|
|
3
3
|
const PROJECTION_FALLBACK_WARN_INTERVAL_MS = 5 * 60_000;
|
|
4
|
+
export const PROJECTION_LEDGER_LAG_WARN_THRESHOLD_EVENTS = 100;
|
|
4
5
|
const projectionFallbackWarnedAt = new Map<string, number>();
|
|
5
6
|
|
|
6
7
|
export function assertMemoryFrontmatterId(
|
|
@@ -18,23 +19,43 @@ export function assertMemoryFrontmatterId(
|
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
export interface ProjectionLedgerLagTelemetry {
|
|
23
|
+
projectedEvents: number;
|
|
24
|
+
currentLedgerEvents: number;
|
|
25
|
+
deltaEvents: number;
|
|
26
|
+
warnThresholdEvents?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
21
29
|
export function warnProjectionFallback(
|
|
22
30
|
memoryDir: string,
|
|
23
31
|
consumer: string,
|
|
24
|
-
// Lazy so the (potentially I/O-bound) lag computation runs only when this
|
|
25
|
-
// call actually logs, not on every rate-limited-suppressed fallback (#2119).
|
|
26
32
|
detail?: () => string | undefined,
|
|
33
|
+
ledgerLag?: ProjectionLedgerLagTelemetry,
|
|
27
34
|
): null {
|
|
35
|
+
const fallback = `storage.${consumer}: memory projection absent or empty; falling back to full corpus`;
|
|
36
|
+
const thresholdEvents = Math.max(
|
|
37
|
+
0,
|
|
38
|
+
Math.floor(ledgerLag?.warnThresholdEvents ?? PROJECTION_LEDGER_LAG_WARN_THRESHOLD_EVENTS),
|
|
39
|
+
);
|
|
40
|
+
const lag = ledgerLag
|
|
41
|
+
? `projected_events=${ledgerLag.projectedEvents} current_ledger_events=${ledgerLag.currentLedgerEvents} `
|
|
42
|
+
+ `delta_events=${ledgerLag.deltaEvents} threshold_events=${thresholdEvents} `
|
|
43
|
+
+ "fallback_action=full-ledger-scan"
|
|
44
|
+
: undefined;
|
|
45
|
+
|
|
46
|
+
if (ledgerLag && ledgerLag.deltaEvents <= thresholdEvents) {
|
|
47
|
+
const suffix = [detail?.(), lag].filter(Boolean).join("; ");
|
|
48
|
+
log.debug(`${fallback}${suffix ? ` (${suffix})` : ""}`);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
28
52
|
const key = `${memoryDir}\0${consumer}`;
|
|
29
53
|
const now = Date.now();
|
|
30
54
|
const warnedAt = projectionFallbackWarnedAt.get(key) ?? 0;
|
|
31
55
|
if (now - warnedAt >= PROJECTION_FALLBACK_WARN_INTERVAL_MS) {
|
|
32
56
|
projectionFallbackWarnedAt.set(key, now);
|
|
33
|
-
const suffix = detail?.();
|
|
34
|
-
log.warn(
|
|
35
|
-
`storage.${consumer}: memory projection absent or empty; falling back to full corpus`
|
|
36
|
-
+ (suffix ? ` (${suffix})` : ""),
|
|
37
|
-
);
|
|
57
|
+
const suffix = [detail?.(), lag].filter(Boolean).join("; ");
|
|
58
|
+
log.warn(`${fallback}${suffix ? ` (${suffix})` : ""}`);
|
|
38
59
|
}
|
|
39
60
|
return null;
|
|
40
61
|
}
|