@remnic/core 9.3.706 → 9.3.708
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/schemas.d.ts +22 -22
- package/dist/transfer/types.d.ts +12 -12
- package/package.json +2 -2
- package/src/issue-1492-coding-mode-consistency.test.ts +315 -0
package/dist/schemas.d.ts
CHANGED
|
@@ -290,12 +290,12 @@ declare const EntityMentionSchema: z.ZodObject<{
|
|
|
290
290
|
title: z.ZodString;
|
|
291
291
|
facts: z.ZodArray<z.ZodString, "many">;
|
|
292
292
|
}, "strip", z.ZodTypeAny, {
|
|
293
|
-
key: string;
|
|
294
293
|
title: string;
|
|
294
|
+
key: string;
|
|
295
295
|
facts: string[];
|
|
296
296
|
}, {
|
|
297
|
-
key: string;
|
|
298
297
|
title: string;
|
|
298
|
+
key: string;
|
|
299
299
|
facts: string[];
|
|
300
300
|
}>, "many">>>;
|
|
301
301
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -303,8 +303,8 @@ declare const EntityMentionSchema: z.ZodObject<{
|
|
|
303
303
|
name: string;
|
|
304
304
|
facts: string[];
|
|
305
305
|
structuredSections?: {
|
|
306
|
-
key: string;
|
|
307
306
|
title: string;
|
|
307
|
+
key: string;
|
|
308
308
|
facts: string[];
|
|
309
309
|
}[] | null | undefined;
|
|
310
310
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -313,8 +313,8 @@ declare const EntityMentionSchema: z.ZodObject<{
|
|
|
313
313
|
name: string;
|
|
314
314
|
facts: string[];
|
|
315
315
|
structuredSections?: {
|
|
316
|
-
key: string;
|
|
317
316
|
title: string;
|
|
317
|
+
key: string;
|
|
318
318
|
facts: string[];
|
|
319
319
|
}[] | null | undefined;
|
|
320
320
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -614,12 +614,12 @@ declare const ProactiveExtractionResultSchema: z.ZodObject<{
|
|
|
614
614
|
title: z.ZodString;
|
|
615
615
|
facts: z.ZodArray<z.ZodString, "many">;
|
|
616
616
|
}, "strip", z.ZodTypeAny, {
|
|
617
|
-
key: string;
|
|
618
617
|
title: string;
|
|
618
|
+
key: string;
|
|
619
619
|
facts: string[];
|
|
620
620
|
}, {
|
|
621
|
-
key: string;
|
|
622
621
|
title: string;
|
|
622
|
+
key: string;
|
|
623
623
|
facts: string[];
|
|
624
624
|
}>, "many">>>;
|
|
625
625
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -627,8 +627,8 @@ declare const ProactiveExtractionResultSchema: z.ZodObject<{
|
|
|
627
627
|
name: string;
|
|
628
628
|
facts: string[];
|
|
629
629
|
structuredSections?: {
|
|
630
|
-
key: string;
|
|
631
630
|
title: string;
|
|
631
|
+
key: string;
|
|
632
632
|
facts: string[];
|
|
633
633
|
}[] | null | undefined;
|
|
634
634
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -637,8 +637,8 @@ declare const ProactiveExtractionResultSchema: z.ZodObject<{
|
|
|
637
637
|
name: string;
|
|
638
638
|
facts: string[];
|
|
639
639
|
structuredSections?: {
|
|
640
|
-
key: string;
|
|
641
640
|
title: string;
|
|
641
|
+
key: string;
|
|
642
642
|
facts: string[];
|
|
643
643
|
}[] | null | undefined;
|
|
644
644
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -698,8 +698,8 @@ declare const ProactiveExtractionResultSchema: z.ZodObject<{
|
|
|
698
698
|
name: string;
|
|
699
699
|
facts: string[];
|
|
700
700
|
structuredSections?: {
|
|
701
|
-
key: string;
|
|
702
701
|
title: string;
|
|
702
|
+
key: string;
|
|
703
703
|
facts: string[];
|
|
704
704
|
}[] | null | undefined;
|
|
705
705
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -750,8 +750,8 @@ declare const ProactiveExtractionResultSchema: z.ZodObject<{
|
|
|
750
750
|
name: string;
|
|
751
751
|
facts: string[];
|
|
752
752
|
structuredSections?: {
|
|
753
|
-
key: string;
|
|
754
753
|
title: string;
|
|
754
|
+
key: string;
|
|
755
755
|
facts: string[];
|
|
756
756
|
}[] | null | undefined;
|
|
757
757
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -1003,12 +1003,12 @@ declare const ExtractionResultSchema: z.ZodObject<{
|
|
|
1003
1003
|
title: z.ZodString;
|
|
1004
1004
|
facts: z.ZodArray<z.ZodString, "many">;
|
|
1005
1005
|
}, "strip", z.ZodTypeAny, {
|
|
1006
|
-
key: string;
|
|
1007
1006
|
title: string;
|
|
1007
|
+
key: string;
|
|
1008
1008
|
facts: string[];
|
|
1009
1009
|
}, {
|
|
1010
|
-
key: string;
|
|
1011
1010
|
title: string;
|
|
1011
|
+
key: string;
|
|
1012
1012
|
facts: string[];
|
|
1013
1013
|
}>, "many">>>;
|
|
1014
1014
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -1016,8 +1016,8 @@ declare const ExtractionResultSchema: z.ZodObject<{
|
|
|
1016
1016
|
name: string;
|
|
1017
1017
|
facts: string[];
|
|
1018
1018
|
structuredSections?: {
|
|
1019
|
-
key: string;
|
|
1020
1019
|
title: string;
|
|
1020
|
+
key: string;
|
|
1021
1021
|
facts: string[];
|
|
1022
1022
|
}[] | null | undefined;
|
|
1023
1023
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -1026,8 +1026,8 @@ declare const ExtractionResultSchema: z.ZodObject<{
|
|
|
1026
1026
|
name: string;
|
|
1027
1027
|
facts: string[];
|
|
1028
1028
|
structuredSections?: {
|
|
1029
|
-
key: string;
|
|
1030
1029
|
title: string;
|
|
1030
|
+
key: string;
|
|
1031
1031
|
facts: string[];
|
|
1032
1032
|
}[] | null | undefined;
|
|
1033
1033
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -1106,8 +1106,8 @@ declare const ExtractionResultSchema: z.ZodObject<{
|
|
|
1106
1106
|
name: string;
|
|
1107
1107
|
facts: string[];
|
|
1108
1108
|
structuredSections?: {
|
|
1109
|
-
key: string;
|
|
1110
1109
|
title: string;
|
|
1110
|
+
key: string;
|
|
1111
1111
|
facts: string[];
|
|
1112
1112
|
}[] | null | undefined;
|
|
1113
1113
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -1164,8 +1164,8 @@ declare const ExtractionResultSchema: z.ZodObject<{
|
|
|
1164
1164
|
name: string;
|
|
1165
1165
|
facts: string[];
|
|
1166
1166
|
structuredSections?: {
|
|
1167
|
-
key: string;
|
|
1168
1167
|
title: string;
|
|
1168
|
+
key: string;
|
|
1169
1169
|
facts: string[];
|
|
1170
1170
|
}[] | null | undefined;
|
|
1171
1171
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -1229,12 +1229,12 @@ declare const ConsolidationResultSchema: z.ZodObject<{
|
|
|
1229
1229
|
title: z.ZodString;
|
|
1230
1230
|
facts: z.ZodArray<z.ZodString, "many">;
|
|
1231
1231
|
}, "strip", z.ZodTypeAny, {
|
|
1232
|
-
key: string;
|
|
1233
1232
|
title: string;
|
|
1233
|
+
key: string;
|
|
1234
1234
|
facts: string[];
|
|
1235
1235
|
}, {
|
|
1236
|
-
key: string;
|
|
1237
1236
|
title: string;
|
|
1237
|
+
key: string;
|
|
1238
1238
|
facts: string[];
|
|
1239
1239
|
}>, "many">>>;
|
|
1240
1240
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -1242,8 +1242,8 @@ declare const ConsolidationResultSchema: z.ZodObject<{
|
|
|
1242
1242
|
name: string;
|
|
1243
1243
|
facts: string[];
|
|
1244
1244
|
structuredSections?: {
|
|
1245
|
-
key: string;
|
|
1246
1245
|
title: string;
|
|
1246
|
+
key: string;
|
|
1247
1247
|
facts: string[];
|
|
1248
1248
|
}[] | null | undefined;
|
|
1249
1249
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -1252,8 +1252,8 @@ declare const ConsolidationResultSchema: z.ZodObject<{
|
|
|
1252
1252
|
name: string;
|
|
1253
1253
|
facts: string[];
|
|
1254
1254
|
structuredSections?: {
|
|
1255
|
-
key: string;
|
|
1256
1255
|
title: string;
|
|
1256
|
+
key: string;
|
|
1257
1257
|
facts: string[];
|
|
1258
1258
|
}[] | null | undefined;
|
|
1259
1259
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -1272,8 +1272,8 @@ declare const ConsolidationResultSchema: z.ZodObject<{
|
|
|
1272
1272
|
name: string;
|
|
1273
1273
|
facts: string[];
|
|
1274
1274
|
structuredSections?: {
|
|
1275
|
-
key: string;
|
|
1276
1275
|
title: string;
|
|
1276
|
+
key: string;
|
|
1277
1277
|
facts: string[];
|
|
1278
1278
|
}[] | null | undefined;
|
|
1279
1279
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -1292,8 +1292,8 @@ declare const ConsolidationResultSchema: z.ZodObject<{
|
|
|
1292
1292
|
name: string;
|
|
1293
1293
|
facts: string[];
|
|
1294
1294
|
structuredSections?: {
|
|
1295
|
-
key: string;
|
|
1296
1295
|
title: string;
|
|
1296
|
+
key: string;
|
|
1297
1297
|
facts: string[];
|
|
1298
1298
|
}[] | null | undefined;
|
|
1299
1299
|
promptedByQuestion?: string | null | undefined;
|
package/dist/transfer/types.d.ts
CHANGED
|
@@ -313,13 +313,13 @@ declare const CapsuleBlockSchema: z.ZodObject<{
|
|
|
313
313
|
peerProfiles: boolean;
|
|
314
314
|
}>;
|
|
315
315
|
}, "strip", z.ZodTypeAny, {
|
|
316
|
+
schemaVersion: string;
|
|
316
317
|
includes: {
|
|
317
318
|
procedural: boolean;
|
|
318
319
|
taxonomy: boolean;
|
|
319
320
|
identityAnchors: boolean;
|
|
320
321
|
peerProfiles: boolean;
|
|
321
322
|
};
|
|
322
|
-
schemaVersion: string;
|
|
323
323
|
id: string;
|
|
324
324
|
description: string;
|
|
325
325
|
version: string;
|
|
@@ -334,13 +334,13 @@ declare const CapsuleBlockSchema: z.ZodObject<{
|
|
|
334
334
|
directAnswerEnabled: boolean;
|
|
335
335
|
};
|
|
336
336
|
}, {
|
|
337
|
+
schemaVersion: string;
|
|
337
338
|
includes: {
|
|
338
339
|
procedural: boolean;
|
|
339
340
|
taxonomy: boolean;
|
|
340
341
|
identityAnchors: boolean;
|
|
341
342
|
peerProfiles: boolean;
|
|
342
343
|
};
|
|
343
|
-
schemaVersion: string;
|
|
344
344
|
id: string;
|
|
345
345
|
description: string;
|
|
346
346
|
version: string;
|
|
@@ -464,13 +464,13 @@ declare const ExportManifestV2Schema: z.ZodObject<{
|
|
|
464
464
|
peerProfiles: boolean;
|
|
465
465
|
}>;
|
|
466
466
|
}, "strip", z.ZodTypeAny, {
|
|
467
|
+
schemaVersion: string;
|
|
467
468
|
includes: {
|
|
468
469
|
procedural: boolean;
|
|
469
470
|
taxonomy: boolean;
|
|
470
471
|
identityAnchors: boolean;
|
|
471
472
|
peerProfiles: boolean;
|
|
472
473
|
};
|
|
473
|
-
schemaVersion: string;
|
|
474
474
|
id: string;
|
|
475
475
|
description: string;
|
|
476
476
|
version: string;
|
|
@@ -485,13 +485,13 @@ declare const ExportManifestV2Schema: z.ZodObject<{
|
|
|
485
485
|
directAnswerEnabled: boolean;
|
|
486
486
|
};
|
|
487
487
|
}, {
|
|
488
|
+
schemaVersion: string;
|
|
488
489
|
includes: {
|
|
489
490
|
procedural: boolean;
|
|
490
491
|
taxonomy: boolean;
|
|
491
492
|
identityAnchors: boolean;
|
|
492
493
|
peerProfiles: boolean;
|
|
493
494
|
};
|
|
494
|
-
schemaVersion: string;
|
|
495
495
|
id: string;
|
|
496
496
|
description: string;
|
|
497
497
|
version: string;
|
|
@@ -518,13 +518,13 @@ declare const ExportManifestV2Schema: z.ZodObject<{
|
|
|
518
518
|
pluginVersion: string;
|
|
519
519
|
includesTranscripts: boolean;
|
|
520
520
|
capsule: {
|
|
521
|
+
schemaVersion: string;
|
|
521
522
|
includes: {
|
|
522
523
|
procedural: boolean;
|
|
523
524
|
taxonomy: boolean;
|
|
524
525
|
identityAnchors: boolean;
|
|
525
526
|
peerProfiles: boolean;
|
|
526
527
|
};
|
|
527
|
-
schemaVersion: string;
|
|
528
528
|
id: string;
|
|
529
529
|
description: string;
|
|
530
530
|
version: string;
|
|
@@ -551,13 +551,13 @@ declare const ExportManifestV2Schema: z.ZodObject<{
|
|
|
551
551
|
pluginVersion: string;
|
|
552
552
|
includesTranscripts: boolean;
|
|
553
553
|
capsule: {
|
|
554
|
+
schemaVersion: string;
|
|
554
555
|
includes: {
|
|
555
556
|
procedural: boolean;
|
|
556
557
|
taxonomy: boolean;
|
|
557
558
|
identityAnchors: boolean;
|
|
558
559
|
peerProfiles: boolean;
|
|
559
560
|
};
|
|
560
|
-
schemaVersion: string;
|
|
561
561
|
id: string;
|
|
562
562
|
description: string;
|
|
563
563
|
version: string;
|
|
@@ -683,13 +683,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
683
683
|
peerProfiles: boolean;
|
|
684
684
|
}>;
|
|
685
685
|
}, "strip", z.ZodTypeAny, {
|
|
686
|
+
schemaVersion: string;
|
|
686
687
|
includes: {
|
|
687
688
|
procedural: boolean;
|
|
688
689
|
taxonomy: boolean;
|
|
689
690
|
identityAnchors: boolean;
|
|
690
691
|
peerProfiles: boolean;
|
|
691
692
|
};
|
|
692
|
-
schemaVersion: string;
|
|
693
693
|
id: string;
|
|
694
694
|
description: string;
|
|
695
695
|
version: string;
|
|
@@ -704,13 +704,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
704
704
|
directAnswerEnabled: boolean;
|
|
705
705
|
};
|
|
706
706
|
}, {
|
|
707
|
+
schemaVersion: string;
|
|
707
708
|
includes: {
|
|
708
709
|
procedural: boolean;
|
|
709
710
|
taxonomy: boolean;
|
|
710
711
|
identityAnchors: boolean;
|
|
711
712
|
peerProfiles: boolean;
|
|
712
713
|
};
|
|
713
|
-
schemaVersion: string;
|
|
714
714
|
id: string;
|
|
715
715
|
description: string;
|
|
716
716
|
version: string;
|
|
@@ -737,13 +737,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
737
737
|
pluginVersion: string;
|
|
738
738
|
includesTranscripts: boolean;
|
|
739
739
|
capsule: {
|
|
740
|
+
schemaVersion: string;
|
|
740
741
|
includes: {
|
|
741
742
|
procedural: boolean;
|
|
742
743
|
taxonomy: boolean;
|
|
743
744
|
identityAnchors: boolean;
|
|
744
745
|
peerProfiles: boolean;
|
|
745
746
|
};
|
|
746
|
-
schemaVersion: string;
|
|
747
747
|
id: string;
|
|
748
748
|
description: string;
|
|
749
749
|
version: string;
|
|
@@ -770,13 +770,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
770
770
|
pluginVersion: string;
|
|
771
771
|
includesTranscripts: boolean;
|
|
772
772
|
capsule: {
|
|
773
|
+
schemaVersion: string;
|
|
773
774
|
includes: {
|
|
774
775
|
procedural: boolean;
|
|
775
776
|
taxonomy: boolean;
|
|
776
777
|
identityAnchors: boolean;
|
|
777
778
|
peerProfiles: boolean;
|
|
778
779
|
};
|
|
779
|
-
schemaVersion: string;
|
|
780
780
|
id: string;
|
|
781
781
|
description: string;
|
|
782
782
|
version: string;
|
|
@@ -819,13 +819,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
819
819
|
pluginVersion: string;
|
|
820
820
|
includesTranscripts: boolean;
|
|
821
821
|
capsule: {
|
|
822
|
+
schemaVersion: string;
|
|
822
823
|
includes: {
|
|
823
824
|
procedural: boolean;
|
|
824
825
|
taxonomy: boolean;
|
|
825
826
|
identityAnchors: boolean;
|
|
826
827
|
peerProfiles: boolean;
|
|
827
828
|
};
|
|
828
|
-
schemaVersion: string;
|
|
829
829
|
id: string;
|
|
830
830
|
description: string;
|
|
831
831
|
version: string;
|
|
@@ -858,13 +858,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
858
858
|
pluginVersion: string;
|
|
859
859
|
includesTranscripts: boolean;
|
|
860
860
|
capsule: {
|
|
861
|
+
schemaVersion: string;
|
|
861
862
|
includes: {
|
|
862
863
|
procedural: boolean;
|
|
863
864
|
taxonomy: boolean;
|
|
864
865
|
identityAnchors: boolean;
|
|
865
866
|
peerProfiles: boolean;
|
|
866
867
|
};
|
|
867
|
-
schemaVersion: string;
|
|
868
868
|
id: string;
|
|
869
869
|
description: string;
|
|
870
870
|
version: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/core",
|
|
3
|
-
"version": "9.3.
|
|
3
|
+
"version": "9.3.708",
|
|
4
4
|
"description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -2966,7 +2966,7 @@
|
|
|
2966
2966
|
"core"
|
|
2967
2967
|
],
|
|
2968
2968
|
"peerDependencies": {
|
|
2969
|
-
"@remnic/coding-graph": "^9.3.
|
|
2969
|
+
"@remnic/coding-graph": "^9.3.708"
|
|
2970
2970
|
},
|
|
2971
2971
|
"peerDependenciesMeta": {
|
|
2972
2972
|
"@remnic/coding-graph": {
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue #1492 — Remnic Coding mode inconsistencies.
|
|
3
|
+
*
|
|
4
|
+
* Cross-subsystem characterization of the three coding-mode drifts an external
|
|
5
|
+
* reporter found when wiring per-repo/project namespace isolation for a shared
|
|
6
|
+
* multi-user Remnic deployment. Each reported symptom was fixed by a dedicated
|
|
7
|
+
* child issue; this file is the single #1492-named anchor that ties them
|
|
8
|
+
* together so a regression in ANY of the three subsystems is caught and
|
|
9
|
+
* attributed here.
|
|
10
|
+
*
|
|
11
|
+
* Symptom A (observe handler ignored coding context)
|
|
12
|
+
* → fixed by #1495: observe resolves ONE effective scope plan
|
|
13
|
+
* (`resolveMemoryScopePlan`) whose `writeNamespace` is byte-identical to
|
|
14
|
+
* what `memory_store`/`suggestion_submit` resolve via
|
|
15
|
+
* `resolveCodingScopedWriteNamespace`, so observed turns and explicit
|
|
16
|
+
* writes on the same project-scoped session land in the SAME store a
|
|
17
|
+
* same-session recall reads. Deep plan-level parity is guarded by
|
|
18
|
+
* `access-service-observe-scope.test.ts` (#1495). Here we pin the
|
|
19
|
+
* coding-namespace derivation invariant the drift violated: the scoped
|
|
20
|
+
* namespace is built by exactly ONE `combineNamespaces(principal, overlay)`
|
|
21
|
+
* and is stable/idempotent — never a second ad-hoc combination that could
|
|
22
|
+
* drift between the write and read paths.
|
|
23
|
+
*
|
|
24
|
+
* Symptom B (transcript/summarizer session-key parser conflated sessions)
|
|
25
|
+
* → fixed by #1496: arbitrary session keys (`pi-geek:abc123`, `pi:abc123`,
|
|
26
|
+
* `myuser:abc123`) are first-class `session/<hash>` identities — never the
|
|
27
|
+
* legacy `other/default` bucket — for BOTH transcript.ts and summarizer.ts.
|
|
28
|
+
*
|
|
29
|
+
* Symptom C (collectActiveMemoryPaths scanned only 4 of 12+ category dirs)
|
|
30
|
+
* → fixed by #1497: the QMD-unavailable filesystem fallback recall scans
|
|
31
|
+
* `RECALL_FALLBACK_DIRS` (every recall category dir), so a memory written
|
|
32
|
+
* to e.g. `preferences/` is found when QMD is disabled/missing/unhealthy.
|
|
33
|
+
*
|
|
34
|
+
* The architectural gaps the reporter also called out — the maintenance
|
|
35
|
+
* pipeline being blind to dynamic project-scoped namespaces (#1500), and the
|
|
36
|
+
* admin/webui not surfacing coding context (#1502) — were tracked by their own
|
|
37
|
+
* issues and are both CLOSED.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
import assert from "node:assert/strict";
|
|
41
|
+
import { mkdir, mkdtemp, readdir, rm, writeFile } from "node:fs/promises";
|
|
42
|
+
import type { Dirent } from "node:fs";
|
|
43
|
+
import os from "node:os";
|
|
44
|
+
import path from "node:path";
|
|
45
|
+
import test from "node:test";
|
|
46
|
+
|
|
47
|
+
import {
|
|
48
|
+
combineNamespaces,
|
|
49
|
+
projectNamespaceName,
|
|
50
|
+
projectTagProjectId,
|
|
51
|
+
resolveCodingNamespaceOverlay,
|
|
52
|
+
} from "./coding/coding-namespace.js";
|
|
53
|
+
import {
|
|
54
|
+
SESSION_CHANNEL_TYPE,
|
|
55
|
+
parseSessionIdentity,
|
|
56
|
+
sessionStoragePaths,
|
|
57
|
+
} from "./session-identity.js";
|
|
58
|
+
import { HourlySummarizer } from "./summarizer.js";
|
|
59
|
+
import type { HourlySummary, PluginConfig } from "./types.js";
|
|
60
|
+
import { StorageManager } from "./storage.js";
|
|
61
|
+
import { RECALL_FALLBACK_DIRS } from "./utils/category-dir.js";
|
|
62
|
+
|
|
63
|
+
/** Recursively collect the immediate child directory names under `root` that
|
|
64
|
+
* contain at least one file, into `out`. Used to verify the summarizer wrote
|
|
65
|
+
* each session's summary into its OWN dir (no conflation). */
|
|
66
|
+
async function walkCollectDirs(root: string, out: Set<string>): Promise<void> {
|
|
67
|
+
let entries: Dirent[];
|
|
68
|
+
try {
|
|
69
|
+
entries = await readdir(root, { withFileTypes: true });
|
|
70
|
+
} catch {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
for (const entry of entries) {
|
|
74
|
+
if (!entry.isDirectory()) continue;
|
|
75
|
+
const child = path.join(root, entry.name);
|
|
76
|
+
let childEntries: Dirent[];
|
|
77
|
+
try {
|
|
78
|
+
childEntries = await readdir(child, { withFileTypes: true });
|
|
79
|
+
} catch {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (childEntries.some((e) => e.isFile())) {
|
|
83
|
+
out.add(entry.name);
|
|
84
|
+
} else {
|
|
85
|
+
await walkCollectDirs(child, out);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ─── Symptom A: coding-namespace derivation is stable across write/read ────
|
|
91
|
+
|
|
92
|
+
test("#1492 Symptom A: a project-scoped session derives ONE stable overlay namespace — the write/read convergence point", () => {
|
|
93
|
+
// The reporter's scenario: user "pi-geek" in project "Blend/Supply". Both the
|
|
94
|
+
// observe write path and the recall read path must resolve the SAME scoped
|
|
95
|
+
// namespace. They do so by combining the principal base with the project
|
|
96
|
+
// overlay through exactly ONE helper (combineNamespaces); a second ad-hoc
|
|
97
|
+
// combination is what caused the original drift.
|
|
98
|
+
const principal = "pi-geek";
|
|
99
|
+
const projectId = projectTagProjectId("Blend/Supply");
|
|
100
|
+
const codingContext = {
|
|
101
|
+
projectId,
|
|
102
|
+
branch: null,
|
|
103
|
+
rootPath: projectId,
|
|
104
|
+
defaultBranch: null,
|
|
105
|
+
};
|
|
106
|
+
const overlay = resolveCodingNamespaceOverlay(
|
|
107
|
+
codingContext,
|
|
108
|
+
{ projectScope: true, branchScope: false, globalFallback: false },
|
|
109
|
+
"default",
|
|
110
|
+
);
|
|
111
|
+
assert.ok(overlay, "projectScope=true must yield a coding overlay");
|
|
112
|
+
|
|
113
|
+
const scopedWrite = combineNamespaces(principal, overlay.namespace);
|
|
114
|
+
const scopedRead = combineNamespaces(principal, overlay.namespace);
|
|
115
|
+
|
|
116
|
+
// Write and read converge on the identical scoped namespace.
|
|
117
|
+
assert.equal(scopedRead, scopedWrite);
|
|
118
|
+
// It is well-formed: principal base + project overlay, not the flat default.
|
|
119
|
+
assert.equal(scopedWrite, combineNamespaces(principal, projectNamespaceName(projectId)));
|
|
120
|
+
assert.ok(
|
|
121
|
+
scopedWrite.startsWith(`${principal}-`),
|
|
122
|
+
"scoped namespace must be principal-prefixed, preserving per-user isolation",
|
|
123
|
+
);
|
|
124
|
+
assert.notEqual(scopedWrite, principal, "must layer the project overlay onto the principal base");
|
|
125
|
+
assert.notEqual(scopedWrite, "default", "must not collapse to the flat default namespace");
|
|
126
|
+
// Two principals in the same repo get distinct scoped namespaces.
|
|
127
|
+
const other = combineNamespaces("pi-friend", overlay.namespace);
|
|
128
|
+
assert.notEqual(scopedWrite, other, "per-user isolation: distinct principals stay distinct");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("#1492 Symptom A: combineNamespaces is idempotent under re-derivation (no double-combination drift)", () => {
|
|
132
|
+
// A read path that (incorrectly) re-combined an already-scoped namespace with
|
|
133
|
+
// the overlay would produce `pi-friend-project-x-project-x` and miss the data.
|
|
134
|
+
// Guard the single-combination contract: combineNamespaces joins base+overlay
|
|
135
|
+
// with `-`, so re-combining must change the result (drift detector).
|
|
136
|
+
const principal = "pi-friend";
|
|
137
|
+
const overlayNs = projectNamespaceName(projectTagProjectId("Remnic"));
|
|
138
|
+
const once = combineNamespaces(principal, overlayNs);
|
|
139
|
+
const twice = combineNamespaces(once, overlayNs);
|
|
140
|
+
assert.notEqual(once, twice, "re-combining a scoped namespace must change it (drift detector)");
|
|
141
|
+
assert.ok(once.startsWith(`${principal}-`), "scoped namespace is principal-prefixed");
|
|
142
|
+
assert.ok(!once.includes(`${overlayNs}-${overlayNs}`), "no duplicated overlay fragment");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// ─── Symptom B: arbitrary session keys never conflate ──────────────────────
|
|
146
|
+
|
|
147
|
+
test("#1492 Symptom B: arbitrary session keys get distinct transcript AND summarizer paths, never other/default", async () => {
|
|
148
|
+
// Exact keys from the issue body. Pre-fix these all collapsed into
|
|
149
|
+
// transcripts/other/default/*.jsonl AND summaries/other/default/*.md,
|
|
150
|
+
// conflating every non-agent-prefixed session in BOTH subsystems.
|
|
151
|
+
const keys = ["pi-geek:abc123", "pi:abc123", "myuser:abc123", "pi-friend:def456"];
|
|
152
|
+
|
|
153
|
+
// ── Transcript path (sessionStoragePaths, consumed by transcript.ts) ──
|
|
154
|
+
const transcriptDirs = new Set<string>();
|
|
155
|
+
for (const key of keys) {
|
|
156
|
+
const id = parseSessionIdentity(key);
|
|
157
|
+
assert.equal(id.legacy, false, `${key} must not be treated as a legacy agent: key`);
|
|
158
|
+
assert.equal(id.channelType, SESSION_CHANNEL_TYPE, `${key} channelType must be "session"`);
|
|
159
|
+
assert.notEqual(id.channelType, "other", `${key} must not fall back to "other"`);
|
|
160
|
+
assert.notEqual(id.channelId, "default", `${key} must not fall back to "default"`);
|
|
161
|
+
const paths = sessionStoragePaths(key);
|
|
162
|
+
assert.match(paths.dir, /^session\/[0-9a-f]{16}$/, `${key} transcript dir must be session/<hash>`);
|
|
163
|
+
transcriptDirs.add(paths.dir);
|
|
164
|
+
}
|
|
165
|
+
assert.equal(transcriptDirs.size, keys.length, "each session key must map to a distinct transcript dir");
|
|
166
|
+
|
|
167
|
+
// ── Summarizer path: exercise the REAL HourlySummarizer.saveSummary() so a
|
|
168
|
+
// regression in summarizer.ts itself (reverting to the old parsed-session
|
|
169
|
+
// bucket) is caught here, not just a regression in session-identity.ts.
|
|
170
|
+
// Pre-fix the summarizer derived its dir from the same brittle split(':')
|
|
171
|
+
// parser; this round-trip proves the written summary files land in
|
|
172
|
+
// distinct per-session dirs (no other/default conflation). ──
|
|
173
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "engram-1492-sum-"));
|
|
174
|
+
try {
|
|
175
|
+
const config = {
|
|
176
|
+
memoryDir,
|
|
177
|
+
localLlmEnabled: false,
|
|
178
|
+
localLlmFallback: false,
|
|
179
|
+
localLlmUrl: "http://localhost:1234/v1",
|
|
180
|
+
localLlmModel: "local-model",
|
|
181
|
+
} as PluginConfig;
|
|
182
|
+
const summarizer = new HourlySummarizer(config);
|
|
183
|
+
|
|
184
|
+
const summary: HourlySummary = {
|
|
185
|
+
hour: "2026-06-28T14:00:00.000Z",
|
|
186
|
+
sessionKey: "", // set per key below
|
|
187
|
+
bullets: ["worked on shared memory pool"],
|
|
188
|
+
turnCount: 2,
|
|
189
|
+
generatedAt: "2026-06-28T14:05:00.000Z",
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
for (const key of keys) {
|
|
193
|
+
await summarizer.saveSummary({ ...summary, sessionKey: key });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Walk summaries/hourly/ and collect the distinct per-session dirs that
|
|
197
|
+
// actually received a file. Each must be the encoded session key, never
|
|
198
|
+
// "other" or "default".
|
|
199
|
+
const hourlyRoot = path.join(memoryDir, "summaries", "hourly");
|
|
200
|
+
const sessionDirs = new Set<string>();
|
|
201
|
+
await walkCollectDirs(hourlyRoot, sessionDirs);
|
|
202
|
+
assert.ok(sessionDirs.size >= keys.length, "each session key must produce a distinct summary dir");
|
|
203
|
+
for (const dir of sessionDirs) {
|
|
204
|
+
assert.notEqual(dir, "other", `summary dir "${dir}" must not be the legacy "other" bucket`);
|
|
205
|
+
assert.notEqual(dir, "default", `summary dir "${dir}" must not be the legacy "default" bucket`);
|
|
206
|
+
}
|
|
207
|
+
} finally {
|
|
208
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("#1492 Symptom B: legacy agent: keys keep their readable identity (no regression from the fix)", () => {
|
|
213
|
+
// The fix must not break the legacy 5-part agent:<id>:discord:channel:<chan> shape.
|
|
214
|
+
const id = parseSessionIdentity("agent:generalist:discord:channel:998877");
|
|
215
|
+
assert.equal(id.legacy, true);
|
|
216
|
+
assert.equal(id.channelType, "discord");
|
|
217
|
+
assert.equal(id.channelId, "998877");
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// ─── Symptom C: fallback recall reads EVERY category dir ───────────────────
|
|
221
|
+
|
|
222
|
+
function memoryFile(id: string, category: string, content: string): string {
|
|
223
|
+
const now = new Date().toISOString();
|
|
224
|
+
return [
|
|
225
|
+
"---",
|
|
226
|
+
`id: ${id}`,
|
|
227
|
+
`category: ${category}`,
|
|
228
|
+
`created: ${now}`,
|
|
229
|
+
`updated: ${now}`,
|
|
230
|
+
"confidence: 0.9",
|
|
231
|
+
"importance: 5",
|
|
232
|
+
"tags: []",
|
|
233
|
+
"---",
|
|
234
|
+
"",
|
|
235
|
+
content,
|
|
236
|
+
"",
|
|
237
|
+
].join("\n");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function makeStorage(prefix = "engram-1492-"): Promise<{
|
|
241
|
+
storage: StorageManager;
|
|
242
|
+
baseDir: string;
|
|
243
|
+
cleanup: () => Promise<void>;
|
|
244
|
+
}> {
|
|
245
|
+
const baseDir = await mkdtemp(path.join(os.tmpdir(), prefix));
|
|
246
|
+
const storage = new StorageManager(baseDir);
|
|
247
|
+
await storage.ensureDirectories();
|
|
248
|
+
StorageManager.clearAllStaticCaches();
|
|
249
|
+
storage.invalidateAllMemoriesCacheForDir();
|
|
250
|
+
return {
|
|
251
|
+
storage,
|
|
252
|
+
baseDir,
|
|
253
|
+
cleanup: async () => {
|
|
254
|
+
StorageManager.clearAllStaticCaches();
|
|
255
|
+
await rm(baseDir, { recursive: true, force: true });
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
test("#1492 Symptom C: a memory in a non-legacy-4 category (preferences/) is found by fallback disk recall", async () => {
|
|
261
|
+
// The reporter found collectActiveMemoryPaths scanned only
|
|
262
|
+
// facts/procedures/reasoning-traces/corrections. A memory written to
|
|
263
|
+
// preferences/ was invisible to the QMD-unavailable fallback, so recall
|
|
264
|
+
// returned empty even though the file was on disk.
|
|
265
|
+
const { storage, baseDir, cleanup } = await makeStorage();
|
|
266
|
+
try {
|
|
267
|
+
const preferencesDir = path.join(baseDir, "preferences");
|
|
268
|
+
await mkdir(preferencesDir, { recursive: true });
|
|
269
|
+
await writeFile(
|
|
270
|
+
path.join(preferencesDir, "pref-1.md"),
|
|
271
|
+
memoryFile("pref-1", "preference", "Prefer tabs over spaces in this repo."),
|
|
272
|
+
"utf-8",
|
|
273
|
+
);
|
|
274
|
+
storage.invalidateAllMemoriesCacheForDir();
|
|
275
|
+
|
|
276
|
+
const memories = await storage.readAllMemories();
|
|
277
|
+
const ids = memories.map((m) => m.frontmatter?.id ?? "");
|
|
278
|
+
assert.ok(
|
|
279
|
+
ids.includes("pref-1"),
|
|
280
|
+
"fallback disk recall must read preferences/ — the 4-dir scan bug is fixed",
|
|
281
|
+
);
|
|
282
|
+
} finally {
|
|
283
|
+
await cleanup();
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test("#1492 Symptom C: RECALL_FALLBACK_DIRS covers the categories the issue named as missed", () => {
|
|
288
|
+
// The issue enumerated questions/, entities/, decisions/, preferences/,
|
|
289
|
+
// commitments/, principles/, skills/, relationships/, moments/, rules/,
|
|
290
|
+
// artifacts/ as directories the system writes to but the old 4-dir scan
|
|
291
|
+
// missed. Assert the recall-memory categories among them are now covered.
|
|
292
|
+
// (questions/ is intentionally a non-memory queue dir; artifacts/ and
|
|
293
|
+
// entities/ are non-category content dirs — see category-dir.ts. The recall
|
|
294
|
+
// *memory* categories must all be present.)
|
|
295
|
+
const requiredRecallCategories = [
|
|
296
|
+
"decisions",
|
|
297
|
+
"preferences",
|
|
298
|
+
"commitments",
|
|
299
|
+
"principles",
|
|
300
|
+
"skills",
|
|
301
|
+
"relationships",
|
|
302
|
+
"moments",
|
|
303
|
+
"rules",
|
|
304
|
+
];
|
|
305
|
+
for (const dir of requiredRecallCategories) {
|
|
306
|
+
assert.ok(
|
|
307
|
+
RECALL_FALLBACK_DIRS.includes(dir),
|
|
308
|
+
`RECALL_FALLBACK_DIRS must include ${dir}/ (missed by the pre-fix 4-dir scan)`,
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
// The legacy four are still present.
|
|
312
|
+
for (const dir of ["facts", "procedures", "reasoning-traces", "corrections"]) {
|
|
313
|
+
assert.ok(RECALL_FALLBACK_DIRS.includes(dir), `legacy recall dir ${dir}/ must remain covered`);
|
|
314
|
+
}
|
|
315
|
+
});
|