@remnic/core 9.3.757 → 9.3.759
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 +2 -2
- package/dist/access-admin-ops-surface.js +20 -20
- package/dist/access-audit.js +2 -2
- package/dist/access-boundary.d.ts +2 -2
- package/dist/access-boundary.js +21 -21
- package/dist/access-cli.js +68 -68
- package/dist/access-http.d.ts +2 -2
- package/dist/access-http.js +28 -28
- package/dist/access-identity-continuity-surface.d.ts +166 -0
- package/dist/access-identity-continuity-surface.js +128 -0
- package/dist/access-lcm-surface.d.ts +2 -2
- package/dist/access-lcm-surface.js +20 -20
- package/dist/access-mcp.d.ts +2 -2
- package/dist/access-mcp.js +27 -27
- package/dist/access-observe-write-surface.d.ts +2 -2
- package/dist/access-observe-write-surface.js +20 -20
- package/dist/access-operations-batch.js +24 -24
- package/dist/access-operations.d.ts +2 -2
- package/dist/access-operations.js +26 -26
- package/dist/access-recall-surface.d.ts +2 -2
- package/dist/access-recall-surface.js +20 -20
- package/dist/access-schema.js +5 -5
- package/dist/{access-service-CJwcDoQp.d.ts → access-service-BW-gb3DL.d.ts} +4 -3
- package/dist/access-service.d.ts +2 -2
- package/dist/access-service.js +20 -20
- package/dist/access-surface-catalog.d.ts +2 -2
- package/dist/bootstrap.d.ts +1 -1
- package/dist/briefing.js +3 -3
- package/dist/{capsule-crypto-RAARDY3M.js → capsule-crypto-CZJSLEFG.js} +2 -2
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.js +8 -8
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory-graph.js +2 -2
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-EEIROWEG.js → chunk-2BFLDH5F.js} +2 -2
- package/dist/{chunk-UPSAT4PY.js → chunk-2MR3MFQB.js} +2 -2
- package/dist/{chunk-MQQO56LN.js → chunk-2XE4F3VZ.js} +30 -30
- package/dist/{chunk-6A76LSX6.js → chunk-3JBW3GHN.js} +2 -2
- package/dist/{chunk-IZP62CEF.js → chunk-3QLPBCFG.js} +4 -4
- package/dist/{chunk-LNQUAJNC.js → chunk-437S3G37.js} +1 -1
- package/dist/{chunk-GI7PRVMV.js → chunk-6CUUFFU7.js} +2 -2
- package/dist/{chunk-6FYRCQ7K.js → chunk-7HWOEYHW.js} +2 -2
- package/dist/{chunk-AGNBY3VG.js → chunk-APJQ6UEA.js} +4 -4
- package/dist/{chunk-PXHY2E3S.js → chunk-AZSSKOG6.js} +4 -4
- package/dist/{chunk-XKUYMM34.js → chunk-DTSOB2YF.js} +2 -2
- package/dist/{chunk-6DMTYBNY.js → chunk-EGQ3AVZV.js} +2 -2
- package/dist/{chunk-XROP4ZHB.js → chunk-EHQ5HO5T.js} +4 -4
- package/dist/{chunk-KTULX4YO.js → chunk-FLVL4L3L.js} +4 -4
- package/dist/{chunk-2NTWULEQ.js → chunk-FYJWEGYP.js} +2 -2
- package/dist/{chunk-7HJ3XBMD.js → chunk-ILVF6TIY.js} +2 -2
- package/dist/{chunk-L5DU7THE.js → chunk-IOZZG75J.js} +2 -2
- package/dist/{chunk-4L2KHKLY.js → chunk-IZLGHKWA.js} +271 -197
- package/dist/chunk-IZLGHKWA.js.map +1 -0
- package/dist/{chunk-AINK3KDE.js → chunk-LLVKDFNU.js} +2 -2
- package/dist/{chunk-GIJDW2YO.js → chunk-M2AAYJVI.js} +3 -3
- package/dist/{chunk-CYWCKFCL.js → chunk-M5QKGHCR.js} +5 -5
- package/dist/{chunk-ZL3Y3ZTQ.js → chunk-OLEBPUZ7.js} +5 -5
- package/dist/{chunk-ZG2WP425.js → chunk-ORAK3LNZ.js} +2 -2
- package/dist/{chunk-SUN6XVNI.js → chunk-P26O6R3Q.js} +3 -3
- package/dist/{chunk-FFPH47IX.js → chunk-PKRRBRY2.js} +2 -2
- package/dist/{chunk-PHWBKQHK.js → chunk-QOM3F6YW.js} +8 -8
- package/dist/{chunk-FBS6V6LJ.js → chunk-QQJMVF6P.js} +4 -4
- package/dist/{chunk-T3UW6NRQ.js → chunk-QUGPXPFJ.js} +7 -7
- package/dist/{chunk-LVMVYLNB.js → chunk-SLAZZXBU.js} +2 -2
- package/dist/{chunk-RQXVVENY.js → chunk-SMLMH7S5.js} +174 -135
- package/dist/chunk-SMLMH7S5.js.map +1 -0
- package/dist/{chunk-IR6ATBUI.js → chunk-SN2EWIAI.js} +2 -2
- package/dist/{chunk-ZE7GJ76H.js → chunk-SQNED75R.js} +1 -1
- package/dist/{chunk-PMBWVQ7D.js → chunk-TQT3USCG.js} +2 -2
- package/dist/{chunk-7K5Q6COX.js → chunk-TVVEYCNW.js} +4 -4
- package/dist/{chunk-I65IQD7Y.js → chunk-WIXQ3F5P.js} +2 -2
- package/dist/{chunk-IF6BTZCB.js → chunk-XQT5GSWU.js} +2 -2
- package/dist/{chunk-5PHPNUZN.js → chunk-YP23FQPT.js} +288 -233
- package/dist/chunk-YP23FQPT.js.map +1 -0
- package/dist/{chunk-QVQG7W2H.js → chunk-ZEQVS6AZ.js} +2 -2
- package/dist/{chunk-CTGK5VK3.js → chunk-ZQNCHPTZ.js} +2 -2
- package/dist/{cli-cZyANcmY.d.ts → cli-D551dnva.d.ts} +2 -2
- package/dist/cli.d.ts +3 -3
- package/dist/cli.js +46 -46
- package/dist/compounding/engine.js +3 -3
- package/dist/connectors/codex-materialize-runner.js +3 -3
- package/dist/connectors/index.js +3 -3
- package/dist/contradiction/index.js +4 -4
- package/dist/entity-retrieval.js +3 -3
- package/dist/explicit-capture.d.ts +1 -1
- package/dist/{first-start-migration-RYY7PPGF.js → first-start-migration-DY6YCRC2.js} +4 -4
- package/dist/{graph-edge-decay-U2GKDAVM.js → graph-edge-decay-RXUCJIN4.js} +4 -4
- package/dist/graph-edge-decay-RXUCJIN4.js.map +1 -0
- package/dist/graph-snapshot.js +3 -3
- package/dist/graph.js +2 -2
- package/dist/index.d.ts +4 -4
- package/dist/index.js +85 -85
- package/dist/lcm/index.js +3 -3
- package/dist/maintenance/memory-governance.js +3 -3
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
- package/dist/maintenance/rebuild-memory-projection.js +4 -4
- package/dist/mcp-memory-inspector-app.d.ts +2 -2
- package/dist/namespaces/migrate.js +7 -7
- package/dist/namespaces/search.js +3 -3
- package/dist/namespaces/storage.js +3 -3
- package/dist/operator-toolkit.js +12 -12
- package/dist/orchestration/maintenance.js +5 -5
- package/dist/{orchestrator-Xm6sK5jw.d.ts → orchestrator-BnM187cb.d.ts} +2 -1
- package/dist/orchestrator.d.ts +1 -1
- package/dist/orchestrator.js +70 -70
- package/dist/search/factory.js +2 -2
- package/dist/search/index.js +4 -4
- package/dist/semantic-consolidation.js +4 -4
- package/dist/semantic-rule-promotion.js +3 -3
- package/dist/semantic-rule-verifier.js +3 -3
- package/dist/storage.d.ts +3 -4
- package/dist/storage.js +2 -2
- package/dist/transfer/backup.js +3 -3
- package/dist/transfer/capsule-export.js +4 -4
- package/dist/transfer/capsule-import.js +3 -3
- package/dist/transfer/import-sqlite.js +2 -2
- package/dist/verified-recall.js +3 -3
- package/package.json +2 -2
- package/src/access-identity-continuity-surface.test.ts +374 -0
- package/src/access-identity-continuity-surface.ts +322 -0
- package/src/access-service.ts +23 -171
- package/src/orchestration/recall-background-writes.ts +24 -0
- package/src/orchestration/recall-entry.ts +5 -0
- package/src/orchestration/recall-internal.ts +15 -11
- package/src/orchestration/recall-introspection.ts +5 -0
- package/src/orchestrator-background-writes.test.ts +101 -0
- package/src/orchestrator.ts +13 -13
- package/src/storage/identity-continuity-store.ts +223 -0
- package/src/storage-contract/round-trip.test.ts +5 -0
- package/src/storage.ts +27 -145
- package/src/trust-score-recall-paths.test.ts +2 -0
- package/dist/chunk-4L2KHKLY.js.map +0 -1
- package/dist/chunk-5PHPNUZN.js.map +0 -1
- package/dist/chunk-RQXVVENY.js.map +0 -1
- /package/dist/{capsule-crypto-RAARDY3M.js.map → access-identity-continuity-surface.js.map} +0 -0
- /package/dist/{graph-edge-decay-U2GKDAVM.js.map → capsule-crypto-CZJSLEFG.js.map} +0 -0
- /package/dist/{chunk-EEIROWEG.js.map → chunk-2BFLDH5F.js.map} +0 -0
- /package/dist/{chunk-UPSAT4PY.js.map → chunk-2MR3MFQB.js.map} +0 -0
- /package/dist/{chunk-MQQO56LN.js.map → chunk-2XE4F3VZ.js.map} +0 -0
- /package/dist/{chunk-6A76LSX6.js.map → chunk-3JBW3GHN.js.map} +0 -0
- /package/dist/{chunk-IZP62CEF.js.map → chunk-3QLPBCFG.js.map} +0 -0
- /package/dist/{chunk-LNQUAJNC.js.map → chunk-437S3G37.js.map} +0 -0
- /package/dist/{chunk-GI7PRVMV.js.map → chunk-6CUUFFU7.js.map} +0 -0
- /package/dist/{chunk-6FYRCQ7K.js.map → chunk-7HWOEYHW.js.map} +0 -0
- /package/dist/{chunk-AGNBY3VG.js.map → chunk-APJQ6UEA.js.map} +0 -0
- /package/dist/{chunk-PXHY2E3S.js.map → chunk-AZSSKOG6.js.map} +0 -0
- /package/dist/{chunk-XKUYMM34.js.map → chunk-DTSOB2YF.js.map} +0 -0
- /package/dist/{chunk-6DMTYBNY.js.map → chunk-EGQ3AVZV.js.map} +0 -0
- /package/dist/{chunk-XROP4ZHB.js.map → chunk-EHQ5HO5T.js.map} +0 -0
- /package/dist/{chunk-KTULX4YO.js.map → chunk-FLVL4L3L.js.map} +0 -0
- /package/dist/{chunk-2NTWULEQ.js.map → chunk-FYJWEGYP.js.map} +0 -0
- /package/dist/{chunk-7HJ3XBMD.js.map → chunk-ILVF6TIY.js.map} +0 -0
- /package/dist/{chunk-L5DU7THE.js.map → chunk-IOZZG75J.js.map} +0 -0
- /package/dist/{chunk-AINK3KDE.js.map → chunk-LLVKDFNU.js.map} +0 -0
- /package/dist/{chunk-GIJDW2YO.js.map → chunk-M2AAYJVI.js.map} +0 -0
- /package/dist/{chunk-CYWCKFCL.js.map → chunk-M5QKGHCR.js.map} +0 -0
- /package/dist/{chunk-ZL3Y3ZTQ.js.map → chunk-OLEBPUZ7.js.map} +0 -0
- /package/dist/{chunk-ZG2WP425.js.map → chunk-ORAK3LNZ.js.map} +0 -0
- /package/dist/{chunk-SUN6XVNI.js.map → chunk-P26O6R3Q.js.map} +0 -0
- /package/dist/{chunk-FFPH47IX.js.map → chunk-PKRRBRY2.js.map} +0 -0
- /package/dist/{chunk-PHWBKQHK.js.map → chunk-QOM3F6YW.js.map} +0 -0
- /package/dist/{chunk-FBS6V6LJ.js.map → chunk-QQJMVF6P.js.map} +0 -0
- /package/dist/{chunk-T3UW6NRQ.js.map → chunk-QUGPXPFJ.js.map} +0 -0
- /package/dist/{chunk-LVMVYLNB.js.map → chunk-SLAZZXBU.js.map} +0 -0
- /package/dist/{chunk-IR6ATBUI.js.map → chunk-SN2EWIAI.js.map} +0 -0
- /package/dist/{chunk-ZE7GJ76H.js.map → chunk-SQNED75R.js.map} +0 -0
- /package/dist/{chunk-PMBWVQ7D.js.map → chunk-TQT3USCG.js.map} +0 -0
- /package/dist/{chunk-7K5Q6COX.js.map → chunk-TVVEYCNW.js.map} +0 -0
- /package/dist/{chunk-I65IQD7Y.js.map → chunk-WIXQ3F5P.js.map} +0 -0
- /package/dist/{chunk-IF6BTZCB.js.map → chunk-XQT5GSWU.js.map} +0 -0
- /package/dist/{chunk-QVQG7W2H.js.map → chunk-ZEQVS6AZ.js.map} +0 -0
- /package/dist/{chunk-CTGK5VK3.js.map → chunk-ZQNCHPTZ.js.map} +0 -0
- /package/dist/{first-start-migration-RYY7PPGF.js.map → first-start-migration-DY6YCRC2.js.map} +0 -0
|
@@ -326,6 +326,7 @@ export interface RecallInternalDeps {
|
|
|
326
326
|
): number | undefined;
|
|
327
327
|
getStorage(namespace?: string): Promise<StorageManager>;
|
|
328
328
|
readonly handleHistory: RecallHandleHistoryStore;
|
|
329
|
+
trackRecallBackgroundWrite(promise: Promise<void>, label: string): void;
|
|
329
330
|
isRecallSectionEnabled(
|
|
330
331
|
sectionId: string,
|
|
331
332
|
defaultEnabled?: boolean,
|
|
@@ -908,8 +909,8 @@ export class RecallInternalCoordinator {
|
|
|
908
909
|
}
|
|
909
910
|
}
|
|
910
911
|
if (sessionKey) {
|
|
911
|
-
this.deps.
|
|
912
|
-
.record({
|
|
912
|
+
this.deps.trackRecallBackgroundWrite(
|
|
913
|
+
this.deps.lastRecall.record({
|
|
913
914
|
sessionKey,
|
|
914
915
|
query: retrievalQuery,
|
|
915
916
|
memoryIds: [],
|
|
@@ -935,8 +936,9 @@ export class RecallInternalCoordinator {
|
|
|
935
936
|
injectedChars: identityInjectedChars,
|
|
936
937
|
truncated: identityInjectionTruncated,
|
|
937
938
|
},
|
|
938
|
-
})
|
|
939
|
-
|
|
939
|
+
}),
|
|
940
|
+
"last recall record",
|
|
941
|
+
);
|
|
940
942
|
}
|
|
941
943
|
if (sessionKey) {
|
|
942
944
|
this.deps.queueEvalShadowRecall({
|
|
@@ -5364,8 +5366,8 @@ export class RecallInternalCoordinator {
|
|
|
5364
5366
|
|
|
5365
5367
|
if (sessionKey) {
|
|
5366
5368
|
throwIfRecallAborted(options.abortSignal);
|
|
5367
|
-
this.deps.
|
|
5368
|
-
.record({
|
|
5369
|
+
this.deps.trackRecallBackgroundWrite(
|
|
5370
|
+
this.deps.lastRecall.record({
|
|
5369
5371
|
sessionKey,
|
|
5370
5372
|
query: retrievalQuery,
|
|
5371
5373
|
memoryIds: recalledMemoryIds,
|
|
@@ -5397,8 +5399,9 @@ export class RecallInternalCoordinator {
|
|
|
5397
5399
|
// reviews on #1544).
|
|
5398
5400
|
backendDegradations:
|
|
5399
5401
|
backendDegradations.length > 0 ? backendDegradations : undefined,
|
|
5400
|
-
})
|
|
5401
|
-
|
|
5402
|
+
}),
|
|
5403
|
+
"last recall record",
|
|
5404
|
+
);
|
|
5402
5405
|
// Issue #1582 — record the admitted memory-id set for handle resolution.
|
|
5403
5406
|
// Only when handles are enabled: if injection is off, no handle is ever
|
|
5404
5407
|
// rendered, so there is nothing to resolve and we skip the write.
|
|
@@ -5410,9 +5413,10 @@ export class RecallInternalCoordinator {
|
|
|
5410
5413
|
MEMORY_ID_PATTERN.test(id),
|
|
5411
5414
|
);
|
|
5412
5415
|
if (handleEligibleIds.length > 0) {
|
|
5413
|
-
this.deps.
|
|
5414
|
-
.record(sessionKey, handleEligibleIds)
|
|
5415
|
-
|
|
5416
|
+
this.deps.trackRecallBackgroundWrite(
|
|
5417
|
+
this.deps.handleHistory.record(sessionKey, handleEligibleIds),
|
|
5418
|
+
"handle history record",
|
|
5419
|
+
);
|
|
5416
5420
|
}
|
|
5417
5421
|
}
|
|
5418
5422
|
}
|
|
@@ -66,6 +66,7 @@ export interface RecallIntrospectionDeps {
|
|
|
66
66
|
getStorage(namespace?: string): Promise<StorageManager>;
|
|
67
67
|
readonly graphRecallCoordinator: GraphRecallCoordinator;
|
|
68
68
|
readonly lastRecall: LastRecallStore;
|
|
69
|
+
trackRecallBackgroundWrite(promise: Promise<void>, label: string): void;
|
|
69
70
|
resolveStateDirForNamespace(
|
|
70
71
|
namespace: string,
|
|
71
72
|
): Promise<string>;
|
|
@@ -500,6 +501,10 @@ export class RecallIntrospectionCoordinator {
|
|
|
500
501
|
log.debug(`direct-answer observation chain error: ${err}`);
|
|
501
502
|
}
|
|
502
503
|
});
|
|
504
|
+
this.deps.trackRecallBackgroundWrite(
|
|
505
|
+
this.deps.directAnswerObservationChain,
|
|
506
|
+
"direct-answer observation",
|
|
507
|
+
);
|
|
503
508
|
}
|
|
504
509
|
|
|
505
510
|
async annotateDirectAnswerTier(
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
import { parseConfig } from "./config.js";
|
|
7
|
+
import { Orchestrator } from "./orchestrator.js";
|
|
8
|
+
|
|
9
|
+
test("destroy cancels maintenance and waits for tracked writes before disposing search backends", async () => {
|
|
10
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-background-writes-"));
|
|
11
|
+
const orchestrator = new Orchestrator(
|
|
12
|
+
parseConfig({
|
|
13
|
+
openaiApiKey: "sk-test",
|
|
14
|
+
memoryDir,
|
|
15
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
16
|
+
qmdEnabled: false,
|
|
17
|
+
knowledgeIndexEnabled: false,
|
|
18
|
+
identityContinuityEnabled: false,
|
|
19
|
+
transcriptEnabled: false,
|
|
20
|
+
hourlySummariesEnabled: false,
|
|
21
|
+
compoundingEnabled: false,
|
|
22
|
+
})
|
|
23
|
+
);
|
|
24
|
+
const maintenanceScheduler = (orchestrator as unknown as { maintenanceScheduler: { dispose(): void } })
|
|
25
|
+
.maintenanceScheduler;
|
|
26
|
+
const originalMaintenanceDispose = maintenanceScheduler.dispose.bind(maintenanceScheduler);
|
|
27
|
+
let maintenanceDisposed = false;
|
|
28
|
+
maintenanceScheduler.dispose = () => {
|
|
29
|
+
maintenanceDisposed = true;
|
|
30
|
+
originalMaintenanceDispose();
|
|
31
|
+
};
|
|
32
|
+
const writeGate = Promise.withResolvers<void>();
|
|
33
|
+
let destroySettled = false;
|
|
34
|
+
let destroyPromise: Promise<void> | undefined;
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
orchestrator.trackRecallBackgroundWrite(writeGate.promise, "test background write");
|
|
38
|
+
destroyPromise = orchestrator.destroy().then(() => {
|
|
39
|
+
destroySettled = true;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
43
|
+
assert.equal(destroySettled, false, "destroy must remain pending while a tracked write is pending");
|
|
44
|
+
assert.equal(maintenanceDisposed, true, "destroy must cancel maintenance before waiting for writes");
|
|
45
|
+
|
|
46
|
+
writeGate.resolve();
|
|
47
|
+
await destroyPromise;
|
|
48
|
+
assert.equal(destroySettled, true);
|
|
49
|
+
} finally {
|
|
50
|
+
writeGate.resolve();
|
|
51
|
+
await (destroyPromise ?? orchestrator.destroy());
|
|
52
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("destroy waits for a recall-initiated last-recall write", async () => {
|
|
57
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-recall-background-write-"));
|
|
58
|
+
const orchestrator = new Orchestrator(
|
|
59
|
+
parseConfig({
|
|
60
|
+
openaiApiKey: "sk-test",
|
|
61
|
+
memoryDir,
|
|
62
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
63
|
+
qmdEnabled: false,
|
|
64
|
+
knowledgeIndexEnabled: false,
|
|
65
|
+
identityContinuityEnabled: false,
|
|
66
|
+
transcriptEnabled: false,
|
|
67
|
+
hourlySummariesEnabled: false,
|
|
68
|
+
compoundingEnabled: false,
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
const writeStarted = Promise.withResolvers<void>();
|
|
72
|
+
const writeGate = Promise.withResolvers<void>();
|
|
73
|
+
const originalRecord = orchestrator.lastRecall.record.bind(orchestrator.lastRecall);
|
|
74
|
+
orchestrator.lastRecall.record = async (...args: Parameters<typeof originalRecord>) => {
|
|
75
|
+
writeStarted.resolve();
|
|
76
|
+
await writeGate.promise;
|
|
77
|
+
await originalRecord(...args);
|
|
78
|
+
};
|
|
79
|
+
let destroySettled = false;
|
|
80
|
+
let destroyPromise: Promise<void> | undefined;
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
await orchestrator.initialize();
|
|
84
|
+
await orchestrator.recall("recall state drain contract", "session-background-write");
|
|
85
|
+
await writeStarted.promise;
|
|
86
|
+
destroyPromise = orchestrator.destroy().then(() => {
|
|
87
|
+
destroySettled = true;
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
91
|
+
assert.equal(destroySettled, false, "destroy must wait for the recall-initiated state write");
|
|
92
|
+
|
|
93
|
+
writeGate.resolve();
|
|
94
|
+
await destroyPromise;
|
|
95
|
+
assert.equal(destroySettled, true);
|
|
96
|
+
} finally {
|
|
97
|
+
writeGate.resolve();
|
|
98
|
+
await (destroyPromise ?? orchestrator.destroy());
|
|
99
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
100
|
+
}
|
|
101
|
+
});
|
package/src/orchestrator.ts
CHANGED
|
@@ -120,6 +120,7 @@ import { NamespaceReadFanoutCoordinator } from "./orchestration/namespace-read-f
|
|
|
120
120
|
import { selfDeps } from "./orchestration/self-deps.js";
|
|
121
121
|
import { RecallEntryCoordinator } from "./orchestration/recall-entry.js";
|
|
122
122
|
import { SessionContextCoordinator } from "./orchestration/session-context.js";
|
|
123
|
+
import { drainRecallWrites, trackRecallWrite } from "./orchestration/recall-background-writes.js";
|
|
123
124
|
import {
|
|
124
125
|
abortRecallError,
|
|
125
126
|
buildCompressionGuidelinesMarkdown,
|
|
@@ -135,9 +136,8 @@ import {
|
|
|
135
136
|
type QueryAwarePrefilter,
|
|
136
137
|
type RecallInvocationOptions,
|
|
137
138
|
} from "./orchestration/orchestrator-helpers.js";
|
|
138
|
-
// Issue #1526 seam 25:
|
|
139
|
-
//
|
|
140
|
-
// importers (coordinators, tests, root shims) keep working.
|
|
139
|
+
// Issue #1526 seam 25: helpers moved to orchestration/orchestrator-helpers.ts and are
|
|
140
|
+
// re-exported here so existing importers (coordinators, tests, root shims) keep working.
|
|
141
141
|
export {
|
|
142
142
|
BulkImportBatchPartialFailureError,
|
|
143
143
|
COMPACTION_SIGNAL_MAX_AGE_MS,
|
|
@@ -701,8 +701,7 @@ export class Orchestrator {
|
|
|
701
701
|
/** Read by NamespaceReadFanoutCoordinator (seam 26), hence not `private`. */
|
|
702
702
|
static readonly ARTIFACT_STATUS_CACHE_TTL_MS = 60_000;
|
|
703
703
|
|
|
704
|
-
//
|
|
705
|
-
// Maps memoryId -> {count, lastAccessed} for batched updates
|
|
704
|
+
// Batched access-tracking counts and timestamps (Phase 1A).
|
|
706
705
|
private accessTrackingBuffer: Map<
|
|
707
706
|
string,
|
|
708
707
|
{ count: number; lastAccessed: string }
|
|
@@ -887,9 +886,7 @@ export class Orchestrator {
|
|
|
887
886
|
private utilityRuntimeValues: UtilityRuntimeValues | null = null;
|
|
888
887
|
private evalShadowWriteChain: Promise<void> = Promise.resolve();
|
|
889
888
|
|
|
890
|
-
// Pending
|
|
891
|
-
// Tracks fire-and-forget `annotateDirectAnswerTier` calls so callers (tests,
|
|
892
|
-
// waitForDirectAnswerObservationIdle) can await settlement.
|
|
889
|
+
// Pending fire-and-forget direct-answer tier annotations (#518).
|
|
893
890
|
private directAnswerObservationChain: Promise<void> = Promise.resolve();
|
|
894
891
|
|
|
895
892
|
// Initialization gate: recall() awaits this before proceeding
|
|
@@ -934,8 +931,9 @@ export class Orchestrator {
|
|
|
934
931
|
}
|
|
935
932
|
}
|
|
936
933
|
|
|
937
|
-
|
|
938
|
-
|
|
934
|
+
/** Track a recall-side write so destroy() can wait for it before teardown. @internal */
|
|
935
|
+
trackRecallBackgroundWrite(promise: Promise<void>, label: string): void {
|
|
936
|
+
trackRecallWrite(this, promise, label);
|
|
939
937
|
}
|
|
940
938
|
|
|
941
939
|
/**
|
|
@@ -952,8 +950,9 @@ export class Orchestrator {
|
|
|
952
950
|
this.wearablesAutoSyncHandle = null;
|
|
953
951
|
}
|
|
954
952
|
this.maintenanceScheduler.dispose();
|
|
953
|
+
await drainRecallWrites(this);
|
|
955
954
|
await this.namespaceSearchRouter.dispose();
|
|
956
|
-
await this.
|
|
955
|
+
await (this.qmd as { dispose?: () => void | Promise<void> }).dispose?.();
|
|
957
956
|
if (this.conversationQmd && this.conversationQmd !== this.qmd) {
|
|
958
957
|
await (this.conversationQmd as { dispose?: () => void | Promise<void> }).dispose?.();
|
|
959
958
|
}
|
|
@@ -3713,8 +3712,9 @@ export class Orchestrator {
|
|
|
3713
3712
|
if (
|
|
3714
3713
|
this.accessTrackingBuffer.size >= this.config.accessTrackingBufferMaxSize
|
|
3715
3714
|
) {
|
|
3716
|
-
this.
|
|
3717
|
-
|
|
3715
|
+
this.trackRecallBackgroundWrite(
|
|
3716
|
+
this.flushAccessTracking(),
|
|
3717
|
+
"background access tracking flush",
|
|
3718
3718
|
);
|
|
3719
3719
|
}
|
|
3720
3720
|
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity-continuity persistence extracted from StorageManager.
|
|
3
|
+
*
|
|
4
|
+
* The host keeps the public API while this store owns anchors, incidents,
|
|
5
|
+
* audits, and improvement-loop files. Dependencies stay live through
|
|
6
|
+
* `selfDeps`, preserving secure-store and catalog-write behavior.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readdir } from "node:fs/promises";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import {
|
|
12
|
+
closeContinuityIncidentRecord,
|
|
13
|
+
createContinuityIncidentRecord,
|
|
14
|
+
parseContinuityImprovementLoops,
|
|
15
|
+
parseContinuityIncident,
|
|
16
|
+
reviewContinuityLoopInMarkdown,
|
|
17
|
+
serializeContinuityIncident,
|
|
18
|
+
upsertContinuityLoopInMarkdown,
|
|
19
|
+
} from "../identity-continuity.js";
|
|
20
|
+
import { SecureStoreLockedError } from "../secure-store/secure-fs.js";
|
|
21
|
+
import type {
|
|
22
|
+
ContinuityImprovementLoop,
|
|
23
|
+
ContinuityIncidentCloseInput,
|
|
24
|
+
ContinuityIncidentOpenInput,
|
|
25
|
+
ContinuityIncidentRecord,
|
|
26
|
+
ContinuityLoopReviewInput,
|
|
27
|
+
ContinuityLoopUpsertInput,
|
|
28
|
+
} from "../types.js";
|
|
29
|
+
import { isErrnoCode } from "../utils/errno.js";
|
|
30
|
+
|
|
31
|
+
export interface IdentityContinuityStoreDeps {
|
|
32
|
+
ensureDirectories(): Promise<void>;
|
|
33
|
+
generateId(prefix?: string): string;
|
|
34
|
+
readonly identityAnchorPath: string;
|
|
35
|
+
readonly identityIncidentsDir: string;
|
|
36
|
+
readonly identityAuditsWeeklyDir: string;
|
|
37
|
+
readonly identityAuditsMonthlyDir: string;
|
|
38
|
+
readonly identityImprovementLoopsPath: string;
|
|
39
|
+
readStorageSecureFile(filePath: string): Promise<string>;
|
|
40
|
+
writeStorageSecureFile(filePath: string, content: string | Buffer): Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class IdentityContinuityStore {
|
|
44
|
+
constructor(private readonly deps: IdentityContinuityStoreDeps) {}
|
|
45
|
+
|
|
46
|
+
async writeIdentityAnchor(content: string): Promise<void> {
|
|
47
|
+
await this.deps.ensureDirectories();
|
|
48
|
+
await this.deps.writeStorageSecureFile(this.deps.identityAnchorPath, content);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async readIdentityAnchor(): Promise<string | null> {
|
|
52
|
+
try {
|
|
53
|
+
return await this.deps.readStorageSecureFile(this.deps.identityAnchorPath);
|
|
54
|
+
} catch (err) {
|
|
55
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async appendContinuityIncident(input: ContinuityIncidentOpenInput): Promise<ContinuityIncidentRecord> {
|
|
61
|
+
await this.deps.ensureDirectories();
|
|
62
|
+
const now = new Date();
|
|
63
|
+
const nowIso = now.toISOString();
|
|
64
|
+
const date = nowIso.slice(0, 10);
|
|
65
|
+
const id = this.deps.generateId("incident");
|
|
66
|
+
const incident = createContinuityIncidentRecord(id, input, nowIso);
|
|
67
|
+
const filePath = path.join(this.deps.identityIncidentsDir, `${date}-${id}.md`);
|
|
68
|
+
await this.deps.writeStorageSecureFile(filePath, serializeContinuityIncident(incident));
|
|
69
|
+
return { ...incident, filePath };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async readContinuityIncidents(
|
|
73
|
+
limit = 200,
|
|
74
|
+
state: "open" | "closed" | "all" = "all"
|
|
75
|
+
): Promise<ContinuityIncidentRecord[]> {
|
|
76
|
+
const normalizedLimit = Number.isFinite(limit) ? Math.floor(limit) : 0;
|
|
77
|
+
const cappedLimit = Math.max(0, normalizedLimit);
|
|
78
|
+
if (cappedLimit === 0) return [];
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const candidates = await this.readContinuityIncidentFileNames();
|
|
82
|
+
const incidents: ContinuityIncidentRecord[] = [];
|
|
83
|
+
|
|
84
|
+
for (const file of candidates) {
|
|
85
|
+
if (incidents.length >= cappedLimit) break;
|
|
86
|
+
const filePath = path.join(this.deps.identityIncidentsDir, file);
|
|
87
|
+
try {
|
|
88
|
+
const raw = await this.deps.readStorageSecureFile(filePath);
|
|
89
|
+
const parsed = parseContinuityIncident(raw);
|
|
90
|
+
if (!parsed) continue;
|
|
91
|
+
if (state !== "all" && parsed.state !== state) continue;
|
|
92
|
+
incidents.push({ ...parsed, filePath });
|
|
93
|
+
} catch (err) {
|
|
94
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
95
|
+
// Fail-open on malformed/missing files.
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return incidents;
|
|
99
|
+
} catch (err) {
|
|
100
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
101
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async closeContinuityIncident(
|
|
107
|
+
id: string,
|
|
108
|
+
closure: ContinuityIncidentCloseInput
|
|
109
|
+
): Promise<ContinuityIncidentRecord | null> {
|
|
110
|
+
const directFilePath = await this.findContinuityIncidentFilePathById(id);
|
|
111
|
+
const target = directFilePath ? await this.readContinuityIncidentFile(directFilePath) : null;
|
|
112
|
+
if (!target || !directFilePath) return null;
|
|
113
|
+
if (target.state === "closed") return target;
|
|
114
|
+
|
|
115
|
+
const closed = closeContinuityIncidentRecord(target, closure, new Date().toISOString());
|
|
116
|
+
await this.deps.writeStorageSecureFile(directFilePath, serializeContinuityIncident(closed));
|
|
117
|
+
return { ...closed, filePath: directFilePath };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async writeIdentityAudit(period: "weekly" | "monthly", key: string, content: string): Promise<string> {
|
|
121
|
+
await this.deps.ensureDirectories();
|
|
122
|
+
const safeKey = this.sanitizeIdentityAuditKey(key);
|
|
123
|
+
const dir = period === "weekly" ? this.deps.identityAuditsWeeklyDir : this.deps.identityAuditsMonthlyDir;
|
|
124
|
+
const filePath = path.join(dir, `${safeKey}.md`);
|
|
125
|
+
await this.deps.writeStorageSecureFile(filePath, content);
|
|
126
|
+
return filePath;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async readIdentityAudit(period: "weekly" | "monthly", key: string): Promise<string | null> {
|
|
130
|
+
try {
|
|
131
|
+
const safeKey = this.sanitizeIdentityAuditKey(key);
|
|
132
|
+
const dir = period === "weekly" ? this.deps.identityAuditsWeeklyDir : this.deps.identityAuditsMonthlyDir;
|
|
133
|
+
return await this.deps.readStorageSecureFile(path.join(dir, `${safeKey}.md`));
|
|
134
|
+
} catch (err) {
|
|
135
|
+
if (err instanceof Error && err.message === "Invalid identity audit key") return null;
|
|
136
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async writeIdentityImprovementLoops(content: string): Promise<void> {
|
|
142
|
+
await this.deps.ensureDirectories();
|
|
143
|
+
await this.deps.writeStorageSecureFile(this.deps.identityImprovementLoopsPath, content);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async readIdentityImprovementLoops(): Promise<string | null> {
|
|
147
|
+
try {
|
|
148
|
+
return await this.deps.readStorageSecureFile(this.deps.identityImprovementLoopsPath);
|
|
149
|
+
} catch (err) {
|
|
150
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async readIdentityImprovementLoopRegister(): Promise<ContinuityImprovementLoop[]> {
|
|
156
|
+
const raw = await this.readIdentityImprovementLoops();
|
|
157
|
+
if (!raw) return [];
|
|
158
|
+
return parseContinuityImprovementLoops(raw);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async upsertIdentityImprovementLoop(input: ContinuityLoopUpsertInput): Promise<ContinuityImprovementLoop> {
|
|
162
|
+
const nowIso = new Date().toISOString();
|
|
163
|
+
const raw = await this.readIdentityImprovementLoops();
|
|
164
|
+
const { markdown, loop } = upsertContinuityLoopInMarkdown(raw, input, nowIso);
|
|
165
|
+
await this.writeIdentityImprovementLoops(markdown);
|
|
166
|
+
return loop;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async reviewIdentityImprovementLoop(
|
|
170
|
+
id: string,
|
|
171
|
+
input: ContinuityLoopReviewInput
|
|
172
|
+
): Promise<ContinuityImprovementLoop | null> {
|
|
173
|
+
const raw = await this.readIdentityImprovementLoops();
|
|
174
|
+
const { markdown, loop } = reviewContinuityLoopInMarkdown(raw, id, input, new Date().toISOString());
|
|
175
|
+
if (!loop) return null;
|
|
176
|
+
await this.writeIdentityImprovementLoops(markdown);
|
|
177
|
+
return loop;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private async readContinuityIncidentFileNames(): Promise<string[]> {
|
|
181
|
+
const files = await readdir(this.deps.identityIncidentsDir);
|
|
182
|
+
return files
|
|
183
|
+
.filter((file) => file.endsWith(".md"))
|
|
184
|
+
.sort()
|
|
185
|
+
.reverse();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private async readContinuityIncidentFile(filePath: string): Promise<ContinuityIncidentRecord | null> {
|
|
189
|
+
try {
|
|
190
|
+
const raw = await this.deps.readStorageSecureFile(filePath);
|
|
191
|
+
const parsed = parseContinuityIncident(raw);
|
|
192
|
+
return parsed ? { ...parsed, filePath } : null;
|
|
193
|
+
} catch (err) {
|
|
194
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private async findContinuityIncidentFilePathById(id: string): Promise<string | null> {
|
|
200
|
+
const fileNames = await this.readContinuityIncidentFileNames();
|
|
201
|
+
const directMatch = fileNames.find((name) => name.endsWith(`-${id}.md`));
|
|
202
|
+
if (directMatch) {
|
|
203
|
+
const directPath = path.join(this.deps.identityIncidentsDir, directMatch);
|
|
204
|
+
const parsed = await this.readContinuityIncidentFile(directPath);
|
|
205
|
+
if (parsed?.id === id) return directPath;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
for (const fileName of fileNames) {
|
|
209
|
+
const filePath = path.join(this.deps.identityIncidentsDir, fileName);
|
|
210
|
+
const parsed = await this.readContinuityIncidentFile(filePath);
|
|
211
|
+
if (parsed?.id === id) return filePath;
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private sanitizeIdentityAuditKey(key: string): string {
|
|
217
|
+
const trimmed = key.trim();
|
|
218
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(trimmed) || trimmed.includes("..")) {
|
|
219
|
+
throw new Error("Invalid identity audit key");
|
|
220
|
+
}
|
|
221
|
+
return trimmed;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import assert from "node:assert/strict";
|
|
15
|
+
import { utimes } from "node:fs/promises";
|
|
16
|
+
import path from "node:path";
|
|
15
17
|
import type { MemoryCategory } from "../types.js";
|
|
16
18
|
import test from "node:test";
|
|
17
19
|
|
|
@@ -212,6 +214,9 @@ test("question-queue: writeQuestion → readQuestions → resolveQuestion round-
|
|
|
212
214
|
// resolve marks it resolved
|
|
213
215
|
const resolved = await storage.resolveQuestion(id);
|
|
214
216
|
assert.equal(resolved, true);
|
|
217
|
+
// Directory mtimes are only a cross-process hint and may be coarser than
|
|
218
|
+
// the cache timestamp. Same-process resolution must invalidate directly.
|
|
219
|
+
await utimes(path.join(storage.dir, "questions"), new Date(0), new Date(0));
|
|
215
220
|
|
|
216
221
|
const afterResolve = await storage.readQuestions({ unresolvedOnly: true });
|
|
217
222
|
assert.ok(!afterResolve.some((x) => x.id === id), "resolved question must not appear in unresolvedOnly");
|