@remnic/core 9.3.727 → 9.3.729
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-boundary.d.ts +2 -2
- package/dist/access-boundary.js +6 -6
- package/dist/access-cli.js +11 -11
- package/dist/access-http.d.ts +2 -2
- package/dist/access-http.js +10 -10
- package/dist/access-mcp.d.ts +2 -2
- package/dist/access-mcp.js +9 -9
- package/dist/access-operations-batch.js +7 -7
- package/dist/access-operations.d.ts +8 -8
- package/dist/access-operations.js +8 -8
- package/dist/access-schema.d.ts +46 -46
- package/dist/{access-service-Bgpjxo4f.d.ts → access-service-BXMJqkTA.d.ts} +1 -1
- package/dist/access-service.d.ts +2 -2
- package/dist/access-service.js +5 -5
- package/dist/access-surface-catalog.d.ts +2 -2
- package/dist/bootstrap.d.ts +1 -1
- package/dist/capabilities.d.ts +43 -1
- package/dist/capabilities.js +5 -3
- package/dist/{chunk-DVRHLN2O.js → chunk-3RCBIOOJ.js} +18 -2
- package/dist/{chunk-DVRHLN2O.js.map → chunk-3RCBIOOJ.js.map} +1 -1
- package/dist/{chunk-R7IYU5DZ.js → chunk-4ESAR77N.js} +2 -2
- package/dist/{chunk-SNKPUJHR.js → chunk-76NHR6ZL.js} +3 -3
- package/dist/{chunk-6PY5YJAV.js → chunk-BQCHEV7O.js} +8 -3
- package/dist/{chunk-6PY5YJAV.js.map → chunk-BQCHEV7O.js.map} +1 -1
- package/dist/{chunk-ZA2S2VLL.js → chunk-C2PG7IEH.js} +5 -2
- package/dist/chunk-C2PG7IEH.js.map +1 -0
- package/dist/{chunk-MJIHJ7OG.js → chunk-H5BWGCRZ.js} +11 -10
- package/dist/chunk-H5BWGCRZ.js.map +1 -0
- package/dist/{chunk-XPHVAOBT.js → chunk-HEDZUSCS.js} +180 -101
- package/dist/chunk-HEDZUSCS.js.map +1 -0
- package/dist/{chunk-N3VNUM4Z.js → chunk-HGNOCPZX.js} +4 -4
- package/dist/{chunk-CSRXQ6NB.js → chunk-I5REUWLH.js} +10 -4
- package/dist/chunk-I5REUWLH.js.map +1 -0
- package/dist/{chunk-NSA5ZRUL.js → chunk-M4AYVEWS.js} +3 -3
- package/dist/{chunk-OEEW4KJA.js → chunk-O6YH5F47.js} +3 -3
- package/dist/{chunk-27QQC7ZX.js → chunk-UM5AWJNQ.js} +6 -5
- package/dist/chunk-UM5AWJNQ.js.map +1 -0
- package/dist/{chunk-2KAYTPPT.js → chunk-UTHQPSRC.js} +2 -2
- package/dist/{chunk-CTOQEZSN.js → chunk-V4GNSDFH.js} +2 -2
- package/dist/{chunk-EHISUJFN.js → chunk-WAS3NV46.js} +6 -3
- package/dist/chunk-WAS3NV46.js.map +1 -0
- package/dist/{chunk-DKJCRGWM.js → chunk-YGIQORJ3.js} +2 -2
- package/dist/{cli-BLME420h.d.ts → cli-DmAiDd8A.d.ts} +2 -2
- package/dist/cli.d.ts +3 -3
- package/dist/cli.js +13 -13
- package/dist/embedding-fallback.js +2 -1
- package/dist/explicit-capture.d.ts +1 -1
- package/dist/extraction.js +2 -1
- package/dist/{first-start-migration-PG5HBC3K.js → first-start-migration-22LPFHD5.js} +6 -2
- package/dist/first-start-migration-22LPFHD5.js.map +1 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +16 -16
- package/dist/mcp-memory-inspector-app.d.ts +2 -2
- package/dist/namespaces/migrate.js +5 -4
- package/dist/namespaces/search.js +4 -3
- package/dist/operator-toolkit.js +6 -6
- package/dist/{orchestrator-DqPAz8ot.d.ts → orchestrator-BR0sGsME.d.ts} +104 -26
- package/dist/orchestrator.d.ts +1 -1
- package/dist/orchestrator.js +7 -7
- package/dist/schemas.d.ts +74 -74
- package/dist/search/embed-helper.js +2 -1
- package/dist/search/factory.js +3 -2
- package/dist/search/index.js +3 -2
- package/dist/shared-context/manager.d.ts +2 -2
- package/dist/transfer/types.d.ts +12 -12
- package/package.json +2 -2
- package/src/access-service.ts +2 -1
- package/src/capabilities.test.ts +155 -0
- package/src/capabilities.ts +91 -0
- package/src/chat/chat-engine.test.ts +39 -1
- package/src/chat/chat-session.ts +25 -2
- package/src/cli.ts +3 -2
- package/src/embedding-fallback.ts +2 -1
- package/src/extraction.ts +7 -3
- package/src/maintenance/first-start-migration.ts +3 -1
- package/src/orchestration/extraction-queue-coordinator.ts +175 -0
- package/src/orchestrator-extraction-queue.test.ts +26 -38
- package/src/orchestrator-flush.test.ts +10 -15
- package/src/orchestrator.ts +52 -104
- package/src/search/embed-helper.ts +3 -2
- package/dist/chunk-27QQC7ZX.js.map +0 -1
- package/dist/chunk-CSRXQ6NB.js.map +0 -1
- package/dist/chunk-EHISUJFN.js.map +0 -1
- package/dist/chunk-MJIHJ7OG.js.map +0 -1
- package/dist/chunk-XPHVAOBT.js.map +0 -1
- package/dist/chunk-ZA2S2VLL.js.map +0 -1
- package/dist/first-start-migration-PG5HBC3K.js.map +0 -1
- /package/dist/{chunk-R7IYU5DZ.js.map → chunk-4ESAR77N.js.map} +0 -0
- /package/dist/{chunk-SNKPUJHR.js.map → chunk-76NHR6ZL.js.map} +0 -0
- /package/dist/{chunk-N3VNUM4Z.js.map → chunk-HGNOCPZX.js.map} +0 -0
- /package/dist/{chunk-NSA5ZRUL.js.map → chunk-M4AYVEWS.js.map} +0 -0
- /package/dist/{chunk-OEEW4KJA.js.map → chunk-O6YH5F47.js.map} +0 -0
- /package/dist/{chunk-2KAYTPPT.js.map → chunk-UTHQPSRC.js.map} +0 -0
- /package/dist/{chunk-CTOQEZSN.js.map → chunk-V4GNSDFH.js.map} +0 -0
- /package/dist/{chunk-DKJCRGWM.js.map → chunk-YGIQORJ3.js.map} +0 -0
package/dist/transfer/types.d.ts
CHANGED
|
@@ -313,6 +313,7 @@ declare const CapsuleBlockSchema: z.ZodObject<{
|
|
|
313
313
|
peerProfiles: boolean;
|
|
314
314
|
}>;
|
|
315
315
|
}, "strip", z.ZodTypeAny, {
|
|
316
|
+
id: string;
|
|
316
317
|
includes: {
|
|
317
318
|
procedural: boolean;
|
|
318
319
|
taxonomy: boolean;
|
|
@@ -320,7 +321,6 @@ declare const CapsuleBlockSchema: z.ZodObject<{
|
|
|
320
321
|
peerProfiles: boolean;
|
|
321
322
|
};
|
|
322
323
|
schemaVersion: string;
|
|
323
|
-
id: string;
|
|
324
324
|
version: string;
|
|
325
325
|
description: string;
|
|
326
326
|
parentCapsule: string | null;
|
|
@@ -334,6 +334,7 @@ declare const CapsuleBlockSchema: z.ZodObject<{
|
|
|
334
334
|
directAnswerEnabled: boolean;
|
|
335
335
|
};
|
|
336
336
|
}, {
|
|
337
|
+
id: string;
|
|
337
338
|
includes: {
|
|
338
339
|
procedural: boolean;
|
|
339
340
|
taxonomy: boolean;
|
|
@@ -341,7 +342,6 @@ declare const CapsuleBlockSchema: z.ZodObject<{
|
|
|
341
342
|
peerProfiles: boolean;
|
|
342
343
|
};
|
|
343
344
|
schemaVersion: string;
|
|
344
|
-
id: string;
|
|
345
345
|
version: string;
|
|
346
346
|
description: string;
|
|
347
347
|
parentCapsule: string | null;
|
|
@@ -464,6 +464,7 @@ declare const ExportManifestV2Schema: z.ZodObject<{
|
|
|
464
464
|
peerProfiles: boolean;
|
|
465
465
|
}>;
|
|
466
466
|
}, "strip", z.ZodTypeAny, {
|
|
467
|
+
id: string;
|
|
467
468
|
includes: {
|
|
468
469
|
procedural: boolean;
|
|
469
470
|
taxonomy: boolean;
|
|
@@ -471,7 +472,6 @@ declare const ExportManifestV2Schema: z.ZodObject<{
|
|
|
471
472
|
peerProfiles: boolean;
|
|
472
473
|
};
|
|
473
474
|
schemaVersion: string;
|
|
474
|
-
id: string;
|
|
475
475
|
version: string;
|
|
476
476
|
description: string;
|
|
477
477
|
parentCapsule: string | null;
|
|
@@ -485,6 +485,7 @@ declare const ExportManifestV2Schema: z.ZodObject<{
|
|
|
485
485
|
directAnswerEnabled: boolean;
|
|
486
486
|
};
|
|
487
487
|
}, {
|
|
488
|
+
id: string;
|
|
488
489
|
includes: {
|
|
489
490
|
procedural: boolean;
|
|
490
491
|
taxonomy: boolean;
|
|
@@ -492,7 +493,6 @@ declare const ExportManifestV2Schema: z.ZodObject<{
|
|
|
492
493
|
peerProfiles: boolean;
|
|
493
494
|
};
|
|
494
495
|
schemaVersion: string;
|
|
495
|
-
id: string;
|
|
496
496
|
version: string;
|
|
497
497
|
description: string;
|
|
498
498
|
parentCapsule: string | null;
|
|
@@ -518,6 +518,7 @@ declare const ExportManifestV2Schema: z.ZodObject<{
|
|
|
518
518
|
pluginVersion: string;
|
|
519
519
|
includesTranscripts: boolean;
|
|
520
520
|
capsule: {
|
|
521
|
+
id: string;
|
|
521
522
|
includes: {
|
|
522
523
|
procedural: boolean;
|
|
523
524
|
taxonomy: boolean;
|
|
@@ -525,7 +526,6 @@ declare const ExportManifestV2Schema: z.ZodObject<{
|
|
|
525
526
|
peerProfiles: boolean;
|
|
526
527
|
};
|
|
527
528
|
schemaVersion: string;
|
|
528
|
-
id: string;
|
|
529
529
|
version: string;
|
|
530
530
|
description: string;
|
|
531
531
|
parentCapsule: string | null;
|
|
@@ -551,6 +551,7 @@ declare const ExportManifestV2Schema: z.ZodObject<{
|
|
|
551
551
|
pluginVersion: string;
|
|
552
552
|
includesTranscripts: boolean;
|
|
553
553
|
capsule: {
|
|
554
|
+
id: string;
|
|
554
555
|
includes: {
|
|
555
556
|
procedural: boolean;
|
|
556
557
|
taxonomy: boolean;
|
|
@@ -558,7 +559,6 @@ declare const ExportManifestV2Schema: z.ZodObject<{
|
|
|
558
559
|
peerProfiles: boolean;
|
|
559
560
|
};
|
|
560
561
|
schemaVersion: string;
|
|
561
|
-
id: string;
|
|
562
562
|
version: string;
|
|
563
563
|
description: string;
|
|
564
564
|
parentCapsule: string | null;
|
|
@@ -683,6 +683,7 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
683
683
|
peerProfiles: boolean;
|
|
684
684
|
}>;
|
|
685
685
|
}, "strip", z.ZodTypeAny, {
|
|
686
|
+
id: string;
|
|
686
687
|
includes: {
|
|
687
688
|
procedural: boolean;
|
|
688
689
|
taxonomy: boolean;
|
|
@@ -690,7 +691,6 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
690
691
|
peerProfiles: boolean;
|
|
691
692
|
};
|
|
692
693
|
schemaVersion: string;
|
|
693
|
-
id: string;
|
|
694
694
|
version: string;
|
|
695
695
|
description: string;
|
|
696
696
|
parentCapsule: string | null;
|
|
@@ -704,6 +704,7 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
704
704
|
directAnswerEnabled: boolean;
|
|
705
705
|
};
|
|
706
706
|
}, {
|
|
707
|
+
id: string;
|
|
707
708
|
includes: {
|
|
708
709
|
procedural: boolean;
|
|
709
710
|
taxonomy: boolean;
|
|
@@ -711,7 +712,6 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
711
712
|
peerProfiles: boolean;
|
|
712
713
|
};
|
|
713
714
|
schemaVersion: string;
|
|
714
|
-
id: string;
|
|
715
715
|
version: string;
|
|
716
716
|
description: string;
|
|
717
717
|
parentCapsule: string | null;
|
|
@@ -737,6 +737,7 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
737
737
|
pluginVersion: string;
|
|
738
738
|
includesTranscripts: boolean;
|
|
739
739
|
capsule: {
|
|
740
|
+
id: string;
|
|
740
741
|
includes: {
|
|
741
742
|
procedural: boolean;
|
|
742
743
|
taxonomy: boolean;
|
|
@@ -744,7 +745,6 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
744
745
|
peerProfiles: boolean;
|
|
745
746
|
};
|
|
746
747
|
schemaVersion: string;
|
|
747
|
-
id: string;
|
|
748
748
|
version: string;
|
|
749
749
|
description: string;
|
|
750
750
|
parentCapsule: string | null;
|
|
@@ -770,6 +770,7 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
770
770
|
pluginVersion: string;
|
|
771
771
|
includesTranscripts: boolean;
|
|
772
772
|
capsule: {
|
|
773
|
+
id: string;
|
|
773
774
|
includes: {
|
|
774
775
|
procedural: boolean;
|
|
775
776
|
taxonomy: boolean;
|
|
@@ -777,7 +778,6 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
777
778
|
peerProfiles: boolean;
|
|
778
779
|
};
|
|
779
780
|
schemaVersion: string;
|
|
780
|
-
id: string;
|
|
781
781
|
version: string;
|
|
782
782
|
description: string;
|
|
783
783
|
parentCapsule: string | null;
|
|
@@ -819,6 +819,7 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
819
819
|
pluginVersion: string;
|
|
820
820
|
includesTranscripts: boolean;
|
|
821
821
|
capsule: {
|
|
822
|
+
id: string;
|
|
822
823
|
includes: {
|
|
823
824
|
procedural: boolean;
|
|
824
825
|
taxonomy: boolean;
|
|
@@ -826,7 +827,6 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
826
827
|
peerProfiles: boolean;
|
|
827
828
|
};
|
|
828
829
|
schemaVersion: string;
|
|
829
|
-
id: string;
|
|
830
830
|
version: string;
|
|
831
831
|
description: string;
|
|
832
832
|
parentCapsule: string | null;
|
|
@@ -858,6 +858,7 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
858
858
|
pluginVersion: string;
|
|
859
859
|
includesTranscripts: boolean;
|
|
860
860
|
capsule: {
|
|
861
|
+
id: string;
|
|
861
862
|
includes: {
|
|
862
863
|
procedural: boolean;
|
|
863
864
|
taxonomy: boolean;
|
|
@@ -865,7 +866,6 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
865
866
|
peerProfiles: boolean;
|
|
866
867
|
};
|
|
867
868
|
schemaVersion: string;
|
|
868
|
-
id: string;
|
|
869
869
|
version: string;
|
|
870
870
|
description: string;
|
|
871
871
|
parentCapsule: string | null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/core",
|
|
3
|
-
"version": "9.3.
|
|
3
|
+
"version": "9.3.729",
|
|
4
4
|
"description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -2990,7 +2990,7 @@
|
|
|
2990
2990
|
"core"
|
|
2991
2991
|
],
|
|
2992
2992
|
"peerDependencies": {
|
|
2993
|
-
"@remnic/coding-graph": "^9.3.
|
|
2993
|
+
"@remnic/coding-graph": "^9.3.729"
|
|
2994
2994
|
},
|
|
2995
2995
|
"peerDependenciesMeta": {
|
|
2996
2996
|
"@remnic/coding-graph": {
|
package/src/access-service.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { readdirSync, unlinkSync } from "node:fs";
|
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
6
|
import { ZodError } from "zod";
|
|
7
7
|
import { AccessIdempotencyStore, hashAccessIdempotencyPayload } from "./access-idempotency.js";
|
|
8
|
+
import { resolveMemoryLifecycleCapabilities } from "./capabilities.js";
|
|
8
9
|
import { AccessAuditAdapter, type AccessAuditConfig, type AccessAuditResult } from "./access-audit.js";
|
|
9
10
|
import type { AnomalyDetectorResult } from "./recall-audit-anomaly.js";
|
|
10
11
|
import { resolveGitContext } from "./coding/git-context.js";
|
|
@@ -8187,7 +8188,7 @@ export class EngramAccessService {
|
|
|
8187
8188
|
|
|
8188
8189
|
get embeddingLookupFactoryRef(): (storage: import("./storage.js").StorageManager) => SemanticDedupLookup | undefined {
|
|
8189
8190
|
return (storage) => {
|
|
8190
|
-
if (!this.orchestrator.config.
|
|
8191
|
+
if (!resolveMemoryLifecycleCapabilities(this.orchestrator.config).embeddingFallback) return undefined;
|
|
8191
8192
|
return async (content: string, limit: number) => {
|
|
8192
8193
|
try {
|
|
8193
8194
|
return await this.orchestrator.semanticDedupLookup(content, limit, storage);
|
package/src/capabilities.test.ts
CHANGED
|
@@ -153,6 +153,12 @@ import {
|
|
|
153
153
|
type GraphConstructionCapabilitySet,
|
|
154
154
|
} from "./capabilities.js";
|
|
155
155
|
|
|
156
|
+
import {
|
|
157
|
+
resolveMemoryLifecycleCapabilities,
|
|
158
|
+
type MemoryLifecycleCapabilitySet,
|
|
159
|
+
} from "./capabilities.js";
|
|
160
|
+
|
|
161
|
+
|
|
156
162
|
const GRAPH_FIELD_TO_FLAG: Record<keyof GraphConstructionCapabilitySet, string> = {
|
|
157
163
|
entityGraph: "entityGraphEnabled",
|
|
158
164
|
timeGraph: "timeGraphEnabled",
|
|
@@ -306,3 +312,152 @@ test("resolveGraphConstructionCapabilities matches pre-migration config reads (p
|
|
|
306
312
|
);
|
|
307
313
|
}
|
|
308
314
|
});
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
// ---------------------------------------------------------------------------
|
|
318
|
+
// MemoryLifecycleCapabilitySet — gate-parity tests (issue #1523 batch 3).
|
|
319
|
+
//
|
|
320
|
+
// Same invariant as the sets above: every caps field must project from its
|
|
321
|
+
// `<field>Enabled` config flag, so a future edit that maps a field to the wrong
|
|
322
|
+
// flag fails loudly here. All ten flags are required booleans on PluginConfig
|
|
323
|
+
// (defaults resolved at the parse boundary), so the projection is a pure
|
|
324
|
+
// passthrough — no optional-default variants needed.
|
|
325
|
+
//
|
|
326
|
+
// Parity contract: a caps-resolved run and a config-derived run MUST produce
|
|
327
|
+
// identical boolean values for every gate — on AND off.
|
|
328
|
+
// ---------------------------------------------------------------------------
|
|
329
|
+
|
|
330
|
+
const LIFECYCLE_FIELD_TO_FLAG: Record<keyof MemoryLifecycleCapabilitySet, string> = {
|
|
331
|
+
temporalSupersession: "temporalSupersessionEnabled",
|
|
332
|
+
temporalMemoryTree: "temporalMemoryTreeEnabled",
|
|
333
|
+
lifecyclePolicy: "lifecyclePolicyEnabled",
|
|
334
|
+
lifecycleFilterStale: "lifecycleFilterStaleEnabled",
|
|
335
|
+
lifecycleMetrics: "lifecycleMetricsEnabled",
|
|
336
|
+
extractionScopeClassification: "extractionScopeClassificationEnabled",
|
|
337
|
+
extractionJudge: "extractionJudgeEnabled",
|
|
338
|
+
extractionDedupe: "extractionDedupeEnabled",
|
|
339
|
+
extractionTelemetryPrefilter: "extractionTelemetryPrefilterEnabled",
|
|
340
|
+
extractionJudgeTelemetry: "extractionJudgeTelemetryEnabled",
|
|
341
|
+
embeddingFallback: "embeddingFallbackEnabled",
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const LIFECYCLE_FIELDS = Object.keys(LIFECYCLE_FIELD_TO_FLAG) as Array<
|
|
345
|
+
keyof MemoryLifecycleCapabilitySet
|
|
346
|
+
>;
|
|
347
|
+
|
|
348
|
+
test("resolveMemoryLifecycleCapabilities projects every field from its flag (true variant)", () => {
|
|
349
|
+
const overrides: Record<string, boolean> = {};
|
|
350
|
+
for (const flag of Object.values(LIFECYCLE_FIELD_TO_FLAG)) overrides[flag] = true;
|
|
351
|
+
const config = parseConfig(overrides);
|
|
352
|
+
const caps = resolveMemoryLifecycleCapabilities(config);
|
|
353
|
+
|
|
354
|
+
for (const field of LIFECYCLE_FIELDS) {
|
|
355
|
+
const flag = LIFECYCLE_FIELD_TO_FLAG[field];
|
|
356
|
+
assert.equal(
|
|
357
|
+
caps[field],
|
|
358
|
+
(config as unknown as Record<string, boolean>)[flag],
|
|
359
|
+
`caps.${field} must equal config.${flag} (true variant)`,
|
|
360
|
+
);
|
|
361
|
+
assert.equal(caps[field], true, `caps.${field} should be true here`);
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test("resolveMemoryLifecycleCapabilities projects every field from its flag (false variant)", () => {
|
|
366
|
+
const overrides: Record<string, boolean> = {};
|
|
367
|
+
for (const flag of Object.values(LIFECYCLE_FIELD_TO_FLAG)) overrides[flag] = false;
|
|
368
|
+
const config = parseConfig(overrides);
|
|
369
|
+
const caps = resolveMemoryLifecycleCapabilities(config);
|
|
370
|
+
|
|
371
|
+
for (const field of LIFECYCLE_FIELDS) {
|
|
372
|
+
const flag = LIFECYCLE_FIELD_TO_FLAG[field];
|
|
373
|
+
assert.equal(
|
|
374
|
+
caps[field],
|
|
375
|
+
(config as unknown as Record<string, boolean>)[flag],
|
|
376
|
+
`caps.${field} must equal config.${flag} (false variant)`,
|
|
377
|
+
);
|
|
378
|
+
assert.equal(caps[field], false, `caps.${field} should be false here`);
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test("resolveMemoryLifecycleCapabilities returns a frozen object", () => {
|
|
383
|
+
const config = parseConfig({});
|
|
384
|
+
const caps = resolveMemoryLifecycleCapabilities(config);
|
|
385
|
+
assert.equal(Object.isFrozen(caps), true);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
test("resolveMemoryLifecycleCapabilities matches pre-migration config reads (parity contract)", () => {
|
|
389
|
+
// For representative on/off combinations, the caps-resolved values MUST be
|
|
390
|
+
// identical to what the old scattered `this.config.<flag>Enabled` reads
|
|
391
|
+
// produced. All ten flags used bare reads (no `!== false`/`=== true` at the
|
|
392
|
+
// call site that would change the value — the two sites that wrote
|
|
393
|
+
// `=== true` were redundant coercion on an already-boolean config field),
|
|
394
|
+
// so parity is a direct equality against the resolved config boolean.
|
|
395
|
+
const cases: Record<string, Record<string, boolean>> = {
|
|
396
|
+
"all-off": {
|
|
397
|
+
temporalSupersessionEnabled: false,
|
|
398
|
+
temporalMemoryTreeEnabled: false,
|
|
399
|
+
lifecyclePolicyEnabled: false,
|
|
400
|
+
lifecycleFilterStaleEnabled: false,
|
|
401
|
+
lifecycleMetricsEnabled: false,
|
|
402
|
+
extractionScopeClassificationEnabled: false,
|
|
403
|
+
extractionJudgeEnabled: false,
|
|
404
|
+
extractionDedupeEnabled: false,
|
|
405
|
+
extractionTelemetryPrefilterEnabled: false,
|
|
406
|
+
extractionJudgeTelemetryEnabled: false,
|
|
407
|
+
embeddingFallbackEnabled: false,
|
|
408
|
+
},
|
|
409
|
+
"all-on": {
|
|
410
|
+
temporalSupersessionEnabled: true,
|
|
411
|
+
temporalMemoryTreeEnabled: true,
|
|
412
|
+
lifecyclePolicyEnabled: true,
|
|
413
|
+
lifecycleFilterStaleEnabled: true,
|
|
414
|
+
lifecycleMetricsEnabled: true,
|
|
415
|
+
extractionScopeClassificationEnabled: true,
|
|
416
|
+
extractionJudgeEnabled: true,
|
|
417
|
+
extractionDedupeEnabled: true,
|
|
418
|
+
extractionTelemetryPrefilterEnabled: true,
|
|
419
|
+
extractionJudgeTelemetryEnabled: true,
|
|
420
|
+
embeddingFallbackEnabled: true,
|
|
421
|
+
},
|
|
422
|
+
"temporal-on-rest-off": {
|
|
423
|
+
temporalSupersessionEnabled: true,
|
|
424
|
+
temporalMemoryTreeEnabled: true,
|
|
425
|
+
lifecyclePolicyEnabled: false,
|
|
426
|
+
lifecycleFilterStaleEnabled: false,
|
|
427
|
+
lifecycleMetricsEnabled: false,
|
|
428
|
+
extractionScopeClassificationEnabled: false,
|
|
429
|
+
extractionJudgeEnabled: false,
|
|
430
|
+
extractionDedupeEnabled: false,
|
|
431
|
+
extractionTelemetryPrefilterEnabled: false,
|
|
432
|
+
extractionJudgeTelemetryEnabled: false,
|
|
433
|
+
embeddingFallbackEnabled: false,
|
|
434
|
+
},
|
|
435
|
+
"extraction-on-rest-off": {
|
|
436
|
+
temporalSupersessionEnabled: false,
|
|
437
|
+
temporalMemoryTreeEnabled: false,
|
|
438
|
+
lifecyclePolicyEnabled: false,
|
|
439
|
+
lifecycleFilterStaleEnabled: false,
|
|
440
|
+
lifecycleMetricsEnabled: false,
|
|
441
|
+
extractionScopeClassificationEnabled: true,
|
|
442
|
+
extractionJudgeEnabled: true,
|
|
443
|
+
extractionDedupeEnabled: true,
|
|
444
|
+
extractionTelemetryPrefilterEnabled: true,
|
|
445
|
+
extractionJudgeTelemetryEnabled: true,
|
|
446
|
+
embeddingFallbackEnabled: true,
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
for (const [label, overrides] of Object.entries(cases)) {
|
|
451
|
+
const config = parseConfig(overrides);
|
|
452
|
+
const caps = resolveMemoryLifecycleCapabilities(config);
|
|
453
|
+
|
|
454
|
+
for (const field of LIFECYCLE_FIELDS) {
|
|
455
|
+
const flag = LIFECYCLE_FIELD_TO_FLAG[field];
|
|
456
|
+
assert.equal(
|
|
457
|
+
caps[field],
|
|
458
|
+
(config as unknown as Record<string, boolean>)[flag],
|
|
459
|
+
`[${label}] caps.${field} must equal config.${flag}`,
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
});
|
package/src/capabilities.ts
CHANGED
|
@@ -203,3 +203,94 @@ export function resolveAccessSetupCapabilities(
|
|
|
203
203
|
recallAuditAnomalyDetection: config.recallAuditAnomalyDetectionEnabled === true,
|
|
204
204
|
});
|
|
205
205
|
}
|
|
206
|
+
|
|
207
|
+
// ---------------------------------------------------------------------------
|
|
208
|
+
// Memory-lifecycle capability set (issue #1523 batch 3).
|
|
209
|
+
//
|
|
210
|
+
// The flags below gate memory WRITE/EXTRACTION (extraction judge, scope
|
|
211
|
+
// classification, dedupe, telemetry prefilter), temporal supersession of
|
|
212
|
+
// stored facts, and the lifecycle policy pass (promotion / decay / stale
|
|
213
|
+
// filtering). They are read on the persistExtraction write path, the recall
|
|
214
|
+
// candidate-filter path, and the maintenance / lifecycle-pass path — so, like
|
|
215
|
+
// the graph-construction set above, they get their own projection resolved
|
|
216
|
+
// ONCE at each operation entry that reads them (never re-derived from raw
|
|
217
|
+
// config mid-operation — the gate-divergence defect class #1523 targets).
|
|
218
|
+
//
|
|
219
|
+
// Every field projects from an already-resolved PluginConfig boolean (defaults
|
|
220
|
+
// are applied at the config-parse boundary, rule 36), so the resolver is a
|
|
221
|
+
// pure projection — no `!== false` / `=== true` re-coercion here.
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Frozen projection of memory-lifecycle feature gates (issue #1523 batch 3).
|
|
226
|
+
*
|
|
227
|
+
* Resolved once per write / recall-filter / maintenance operation and threaded
|
|
228
|
+
* down. Composition lives ONLY in {@link resolveMemoryLifecycleCapabilities}.
|
|
229
|
+
*/
|
|
230
|
+
export interface MemoryLifecycleCapabilitySet {
|
|
231
|
+
/** `temporalSupersessionEnabled` — supersede stale structured facts on write, filter on recall. */
|
|
232
|
+
readonly temporalSupersession: boolean;
|
|
233
|
+
/** `temporalMemoryTreeEnabled` — temporal-memory-tree build + recall tier. */
|
|
234
|
+
readonly temporalMemoryTree: boolean;
|
|
235
|
+
/** `lifecyclePolicyEnabled` — lifecycle promotion / decay metadata pass. */
|
|
236
|
+
readonly lifecyclePolicy: boolean;
|
|
237
|
+
/** `lifecycleFilterStaleEnabled` — filter stale-lifecycle memories from recall. */
|
|
238
|
+
readonly lifecycleFilterStale: boolean;
|
|
239
|
+
/** `lifecycleMetricsEnabled` — emit lifecycle-pass metrics. */
|
|
240
|
+
readonly lifecycleMetrics: boolean;
|
|
241
|
+
/** `extractionScopeClassificationEnabled` — LLM classifies each fact's scope. */
|
|
242
|
+
readonly extractionScopeClassification: boolean;
|
|
243
|
+
/** `extractionJudgeEnabled` — LLM-as-judge fact-worthiness gate. */
|
|
244
|
+
readonly extractionJudge: boolean;
|
|
245
|
+
/** `extractionDedupeEnabled` — dedupe against recent extractions. */
|
|
246
|
+
readonly extractionDedupe: boolean;
|
|
247
|
+
/** `extractionTelemetryPrefilterEnabled` — skip mechanical-telemetry transcripts. */
|
|
248
|
+
readonly extractionTelemetryPrefilter: boolean;
|
|
249
|
+
/** `extractionJudgeTelemetryEnabled` — collect judge verdict telemetry. */
|
|
250
|
+
readonly extractionJudgeTelemetry: boolean;
|
|
251
|
+
/** `embeddingFallbackEnabled` — semantic-dedup / archive-search embedding fallback. */
|
|
252
|
+
readonly embeddingFallback: boolean;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Config projection consumed by {@link resolveMemoryLifecycleCapabilities}.
|
|
257
|
+
*/
|
|
258
|
+
export type MemoryLifecycleConfigProjection = Pick<
|
|
259
|
+
PluginConfig,
|
|
260
|
+
| "temporalSupersessionEnabled"
|
|
261
|
+
| "temporalMemoryTreeEnabled"
|
|
262
|
+
| "lifecyclePolicyEnabled"
|
|
263
|
+
| "lifecycleFilterStaleEnabled"
|
|
264
|
+
| "lifecycleMetricsEnabled"
|
|
265
|
+
| "extractionScopeClassificationEnabled"
|
|
266
|
+
| "extractionJudgeEnabled"
|
|
267
|
+
| "extractionDedupeEnabled"
|
|
268
|
+
| "extractionTelemetryPrefilterEnabled"
|
|
269
|
+
| "extractionJudgeTelemetryEnabled"
|
|
270
|
+
| "embeddingFallbackEnabled"
|
|
271
|
+
>;
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Resolve the {@link MemoryLifecycleCapabilitySet} from parsed config.
|
|
275
|
+
*
|
|
276
|
+
* Call this ONCE per write / recall-filter / maintenance operation entry and
|
|
277
|
+
* thread the result down — exactly the pattern {@link resolveCapabilities}
|
|
278
|
+
* established for recall.
|
|
279
|
+
*/
|
|
280
|
+
export function resolveMemoryLifecycleCapabilities(
|
|
281
|
+
config: MemoryLifecycleConfigProjection,
|
|
282
|
+
): MemoryLifecycleCapabilitySet {
|
|
283
|
+
return Object.freeze({
|
|
284
|
+
temporalSupersession: config.temporalSupersessionEnabled,
|
|
285
|
+
temporalMemoryTree: config.temporalMemoryTreeEnabled,
|
|
286
|
+
lifecyclePolicy: config.lifecyclePolicyEnabled,
|
|
287
|
+
lifecycleFilterStale: config.lifecycleFilterStaleEnabled,
|
|
288
|
+
lifecycleMetrics: config.lifecycleMetricsEnabled,
|
|
289
|
+
extractionScopeClassification: config.extractionScopeClassificationEnabled,
|
|
290
|
+
extractionJudge: config.extractionJudgeEnabled,
|
|
291
|
+
extractionDedupe: config.extractionDedupeEnabled,
|
|
292
|
+
extractionTelemetryPrefilter: config.extractionTelemetryPrefilterEnabled,
|
|
293
|
+
extractionJudgeTelemetry: config.extractionJudgeTelemetryEnabled,
|
|
294
|
+
embeddingFallback: config.embeddingFallbackEnabled,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { strict as assert } from "node:assert";
|
|
10
|
-
import { mkdir, rm, readFile, utimes, unlink } from "node:fs/promises";
|
|
10
|
+
import { mkdir, rm, readFile, utimes, unlink, appendFile } from "node:fs/promises";
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import { tmpdir } from "node:os";
|
|
13
13
|
import { test } from "node:test";
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
sessionBelongsToPrincipal,
|
|
23
23
|
chatSessionFile,
|
|
24
24
|
cleanupExpiredChatSessions,
|
|
25
|
+
__resetTranscriptSeqForTest,
|
|
25
26
|
} from "./chat-session.js";
|
|
26
27
|
import type { ChatSessionState } from "./chat-types.js";
|
|
27
28
|
import type { ChatToolExecutor } from "./chat-engine.js";
|
|
@@ -335,3 +336,40 @@ test("appendTranscriptEntry refuses to recreate a swept session (chat_session_ex
|
|
|
335
336
|
/chat_session_expired/,
|
|
336
337
|
);
|
|
337
338
|
});
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
test("loadChatSession raises the seq counter above the on-disk max on backward clock skew (issue #1718)", async () => {
|
|
342
|
+
const dir = await makeTempDir();
|
|
343
|
+
const session = await createChatSession(dir, { principal: "skew" });
|
|
344
|
+
// Simulate a previous process that ran with a forward-skewed clock: append a
|
|
345
|
+
// transcript line whose seq is far in the future relative to the current
|
|
346
|
+
// Date.now(). A later restart with a normal (or backward-stepped) clock must
|
|
347
|
+
// still issue seqs strictly greater than this persisted line.
|
|
348
|
+
const futureSeq = Date.now() + 10_000_000;
|
|
349
|
+
await appendFile(
|
|
350
|
+
chatSessionFile(dir, session.id),
|
|
351
|
+
JSON.stringify({
|
|
352
|
+
seq: futureSeq,
|
|
353
|
+
ts: new Date().toISOString(),
|
|
354
|
+
role: "user",
|
|
355
|
+
content: "future-dated line from a forward-skewed prior process",
|
|
356
|
+
}) + "\n",
|
|
357
|
+
"utf8",
|
|
358
|
+
);
|
|
359
|
+
// Simulate a process restart whose module-level counter starts below the
|
|
360
|
+
// persisted on-disk seq (the backward-clock-skew window).
|
|
361
|
+
__resetTranscriptSeqForTest();
|
|
362
|
+
// Resume the session: loadChatSession must raise the counter above the
|
|
363
|
+
// on-disk max so the next append is strictly greater, regardless of the
|
|
364
|
+
// current Date.now().
|
|
365
|
+
const loaded = await loadChatSession(dir, session.id);
|
|
366
|
+
assert.ok(loaded, "session must load");
|
|
367
|
+
const next = await appendTranscriptEntry(dir, session.id, {
|
|
368
|
+
role: "assistant",
|
|
369
|
+
content: "resumed after backward clock skew",
|
|
370
|
+
});
|
|
371
|
+
assert.ok(
|
|
372
|
+
next.seq > futureSeq,
|
|
373
|
+
`seq ${next.seq} must exceed the on-disk max ${futureSeq} after a backward clock skew (issue #1718)`,
|
|
374
|
+
);
|
|
375
|
+
});
|
package/src/chat/chat-session.ts
CHANGED
|
@@ -50,8 +50,10 @@ export function chatSessionFile(memoryDir: string, chatSessionId: string): strin
|
|
|
50
50
|
* incremented when two appends land in the same millisecond, so `seq` is
|
|
51
51
|
* strictly increasing within a process (review thread: `Date.now()` alone is
|
|
52
52
|
* not monotonic — same-ms entries would share a seq and break the SSE
|
|
53
|
-
* reconnect dedup-by-seq contract). Across process restarts
|
|
54
|
-
*
|
|
53
|
+
* reconnect dedup-by-seq contract). Across process restarts the counter is
|
|
54
|
+
* raised above the on-disk max by `loadChatSession` (issue #1718), so a
|
|
55
|
+
* backward clock step (NTP, VM migration, manual change) cannot re-seed it
|
|
56
|
+
* below lines already persisted for a resumed session.
|
|
55
57
|
*/
|
|
56
58
|
let lastTranscriptSeq = 0;
|
|
57
59
|
|
|
@@ -61,6 +63,14 @@ function nextTranscriptSeq(): number {
|
|
|
61
63
|
return lastTranscriptSeq;
|
|
62
64
|
}
|
|
63
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Test-only: reset the module-level transcript seq counter so a regression
|
|
68
|
+
* test can deterministically simulate a fresh process restart (issue #1718).
|
|
69
|
+
*/
|
|
70
|
+
export function __resetTranscriptSeqForTest(): void {
|
|
71
|
+
lastTranscriptSeq = 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
64
74
|
/**
|
|
65
75
|
* Create a new chat session. The session binds the caller's principal and
|
|
66
76
|
* sessionKey at creation (rule 42); every tool call flows through
|
|
@@ -175,6 +185,19 @@ export async function loadChatSession(
|
|
|
175
185
|
// tolerated, never crash the engine).
|
|
176
186
|
}
|
|
177
187
|
}
|
|
188
|
+
// Issue #1718 — backward-clock-skew robustness: raise the module-level seq
|
|
189
|
+
// counter so the next append is strictly greater than any line already on
|
|
190
|
+
// disk for this session, regardless of clock direction across restarts
|
|
191
|
+
// (NTP step, VM migration, manual change). Without this, a backward clock
|
|
192
|
+
// skew would re-seed the counter below the on-disk max and the SSE
|
|
193
|
+
// reconnect dedup-by-seq contract could drop freshly-appended entries as
|
|
194
|
+
// duplicates. The transcript is already fully parsed above, so this is a
|
|
195
|
+
// single pass over the in-memory array.
|
|
196
|
+
for (const e of transcript) {
|
|
197
|
+
if (typeof e.seq === "number" && e.seq + 1 > lastTranscriptSeq) {
|
|
198
|
+
lastTranscriptSeq = e.seq + 1;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
178
201
|
return {
|
|
179
202
|
id: chatSessionId,
|
|
180
203
|
principal,
|
package/src/cli.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { access, lstat, readFile, readdir, realpath, unlink } from "node:fs/prom
|
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
import type { Readable, Writable } from "node:stream";
|
|
6
6
|
import type { Orchestrator } from "./orchestrator.js";
|
|
7
|
+
import { resolveMemoryLifecycleCapabilities } from "./capabilities.js";
|
|
7
8
|
import { ThreadingManager } from "./threading.js";
|
|
8
9
|
import { utcDayRange } from "./transcript.js";
|
|
9
10
|
import { runWearablesCliCommand } from "./wearables/cli.js";
|
|
@@ -3728,7 +3729,7 @@ export function registerCli(
|
|
|
3728
3729
|
console.log("=== Extraction Judge Verdict Stats ===\n");
|
|
3729
3730
|
console.log(`Total verdicts: ${stats.total}`);
|
|
3730
3731
|
if (stats.total === 0) {
|
|
3731
|
-
if (!orchestrator.config.
|
|
3732
|
+
if (!resolveMemoryLifecycleCapabilities(orchestrator.config).extractionJudgeTelemetry) {
|
|
3732
3733
|
console.log(
|
|
3733
3734
|
"\nNote: extractionJudgeTelemetryEnabled is OFF. Enable it in plugin config to collect verdict telemetry.",
|
|
3734
3735
|
);
|
|
@@ -8541,7 +8542,7 @@ export function registerCli(
|
|
|
8541
8542
|
config: orchestrator.config,
|
|
8542
8543
|
memoryDir: orchestrator.config.memoryDir,
|
|
8543
8544
|
embeddingLookupFactory: (storage) => {
|
|
8544
|
-
if (!orchestrator.config.
|
|
8545
|
+
if (!resolveMemoryLifecycleCapabilities(orchestrator.config).embeddingFallback) return undefined;
|
|
8545
8546
|
return async (content: string, limit: number) => {
|
|
8546
8547
|
try {
|
|
8547
8548
|
return await orchestrator.semanticDedupLookup(content, limit, storage);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { log } from "./logger.js";
|
|
4
|
+
import { resolveMemoryLifecycleCapabilities } from "./capabilities.js";
|
|
4
5
|
import { readEnvVar } from "./runtime/env.js";
|
|
5
6
|
import type { PluginConfig } from "./types.js";
|
|
6
7
|
import {
|
|
@@ -397,7 +398,7 @@ export class EmbeddingFallback {
|
|
|
397
398
|
private async resolveProvider(
|
|
398
399
|
options: { includeHost?: boolean } = {},
|
|
399
400
|
): Promise<ProviderConfig | null> {
|
|
400
|
-
if (!this.config.
|
|
401
|
+
if (!resolveMemoryLifecycleCapabilities(this.config).embeddingFallback) return null;
|
|
401
402
|
|
|
402
403
|
if (
|
|
403
404
|
options.includeHost !== false &&
|