@remnic/core 9.3.518 → 9.3.520
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-schema.d.ts +34 -34
- package/dist/index.d.ts +18 -1
- package/dist/index.js +513 -175
- package/dist/index.js.map +1 -1
- package/dist/schemas.d.ts +42 -42
- package/dist/shared-context/manager.d.ts +2 -2
- package/package.json +1 -1
- package/src/binary-lifecycle/backend.ts +162 -14
- package/src/binary-lifecycle/manifest.ts +24 -12
- package/src/binary-lifecycle/pipeline.test.ts +565 -1
- package/src/binary-lifecycle/pipeline.ts +296 -54
- package/src/binary-lifecycle/types.ts +2 -0
package/dist/schemas.d.ts
CHANGED
|
@@ -7,13 +7,13 @@ declare const MemoryActionEligibilityContextSchema: z.ZodObject<{
|
|
|
7
7
|
importance: z.ZodNumber;
|
|
8
8
|
source: z.ZodEnum<["extraction", "consolidation", "replay", "manual", "unknown"]>;
|
|
9
9
|
}, "strict", z.ZodTypeAny, {
|
|
10
|
-
source: "manual" | "extraction" | "consolidation" | "replay" | "unknown";
|
|
11
10
|
confidence: number;
|
|
11
|
+
source: "unknown" | "manual" | "extraction" | "consolidation" | "replay";
|
|
12
12
|
lifecycleState: "active" | "archived" | "candidate" | "validated" | "stale";
|
|
13
13
|
importance: number;
|
|
14
14
|
}, {
|
|
15
|
-
source: "manual" | "extraction" | "consolidation" | "replay" | "unknown";
|
|
16
15
|
confidence: number;
|
|
16
|
+
source: "unknown" | "manual" | "extraction" | "consolidation" | "replay";
|
|
17
17
|
lifecycleState: "active" | "archived" | "candidate" | "validated" | "stale";
|
|
18
18
|
importance: number;
|
|
19
19
|
}>;
|
|
@@ -149,10 +149,10 @@ declare const ExtractedFactSchema: z.ZodEffects<z.ZodObject<{
|
|
|
149
149
|
observed_outcome?: string | null | undefined;
|
|
150
150
|
}>>>;
|
|
151
151
|
}, "strip", z.ZodTypeAny, {
|
|
152
|
-
tags: string[];
|
|
153
|
-
content: string;
|
|
154
152
|
category: "fact" | "preference" | "correction" | "entity" | "decision" | "relationship" | "principle" | "commitment" | "moment" | "skill" | "rule" | "procedure" | "reasoning_trace";
|
|
153
|
+
content: string;
|
|
155
154
|
confidence: number;
|
|
155
|
+
tags: string[];
|
|
156
156
|
entityRef?: string | null | undefined;
|
|
157
157
|
structuredAttributes?: Record<string, string> | null | undefined;
|
|
158
158
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -178,10 +178,10 @@ declare const ExtractedFactSchema: z.ZodEffects<z.ZodObject<{
|
|
|
178
178
|
observed_outcome?: string | null | undefined;
|
|
179
179
|
} | null | undefined;
|
|
180
180
|
}, {
|
|
181
|
-
tags: string[];
|
|
182
|
-
content: string;
|
|
183
181
|
category: "fact" | "preference" | "correction" | "entity" | "decision" | "relationship" | "principle" | "commitment" | "moment" | "skill" | "rule" | "procedure" | "reasoning_trace";
|
|
182
|
+
content: string;
|
|
184
183
|
confidence: number;
|
|
184
|
+
tags: string[];
|
|
185
185
|
entityRef?: string | null | undefined;
|
|
186
186
|
structuredAttributes?: Record<string, string> | null | undefined;
|
|
187
187
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -207,10 +207,10 @@ declare const ExtractedFactSchema: z.ZodEffects<z.ZodObject<{
|
|
|
207
207
|
observed_outcome?: string | null | undefined;
|
|
208
208
|
} | null | undefined;
|
|
209
209
|
}>, {
|
|
210
|
-
tags: string[];
|
|
211
|
-
content: string;
|
|
212
210
|
category: "fact" | "preference" | "correction" | "entity" | "decision" | "relationship" | "principle" | "commitment" | "moment" | "skill" | "rule" | "procedure" | "reasoning_trace";
|
|
211
|
+
content: string;
|
|
213
212
|
confidence: number;
|
|
213
|
+
tags: string[];
|
|
214
214
|
entityRef?: string | null | undefined;
|
|
215
215
|
structuredAttributes?: Record<string, string> | null | undefined;
|
|
216
216
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -236,10 +236,10 @@ declare const ExtractedFactSchema: z.ZodEffects<z.ZodObject<{
|
|
|
236
236
|
observed_outcome?: string | null | undefined;
|
|
237
237
|
} | null | undefined;
|
|
238
238
|
}, {
|
|
239
|
-
tags: string[];
|
|
240
|
-
content: string;
|
|
241
239
|
category: "fact" | "preference" | "correction" | "entity" | "decision" | "relationship" | "principle" | "commitment" | "moment" | "skill" | "rule" | "procedure" | "reasoning_trace";
|
|
240
|
+
content: string;
|
|
242
241
|
confidence: number;
|
|
242
|
+
tags: string[];
|
|
243
243
|
entityRef?: string | null | undefined;
|
|
244
244
|
structuredAttributes?: Record<string, string> | null | undefined;
|
|
245
245
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -457,10 +457,10 @@ declare const ProactiveExtractionResultSchema: z.ZodObject<{
|
|
|
457
457
|
observed_outcome?: string | null | undefined;
|
|
458
458
|
}>>>;
|
|
459
459
|
}, "strip", z.ZodTypeAny, {
|
|
460
|
-
tags: string[];
|
|
461
|
-
content: string;
|
|
462
460
|
category: "fact" | "preference" | "correction" | "entity" | "decision" | "relationship" | "principle" | "commitment" | "moment" | "skill" | "rule" | "procedure" | "reasoning_trace";
|
|
461
|
+
content: string;
|
|
463
462
|
confidence: number;
|
|
463
|
+
tags: string[];
|
|
464
464
|
entityRef?: string | null | undefined;
|
|
465
465
|
structuredAttributes?: Record<string, string> | null | undefined;
|
|
466
466
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -486,10 +486,10 @@ declare const ProactiveExtractionResultSchema: z.ZodObject<{
|
|
|
486
486
|
observed_outcome?: string | null | undefined;
|
|
487
487
|
} | null | undefined;
|
|
488
488
|
}, {
|
|
489
|
-
tags: string[];
|
|
490
|
-
content: string;
|
|
491
489
|
category: "fact" | "preference" | "correction" | "entity" | "decision" | "relationship" | "principle" | "commitment" | "moment" | "skill" | "rule" | "procedure" | "reasoning_trace";
|
|
490
|
+
content: string;
|
|
492
491
|
confidence: number;
|
|
492
|
+
tags: string[];
|
|
493
493
|
entityRef?: string | null | undefined;
|
|
494
494
|
structuredAttributes?: Record<string, string> | null | undefined;
|
|
495
495
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -515,10 +515,10 @@ declare const ProactiveExtractionResultSchema: z.ZodObject<{
|
|
|
515
515
|
observed_outcome?: string | null | undefined;
|
|
516
516
|
} | null | undefined;
|
|
517
517
|
}>, {
|
|
518
|
-
tags: string[];
|
|
519
|
-
content: string;
|
|
520
518
|
category: "fact" | "preference" | "correction" | "entity" | "decision" | "relationship" | "principle" | "commitment" | "moment" | "skill" | "rule" | "procedure" | "reasoning_trace";
|
|
519
|
+
content: string;
|
|
521
520
|
confidence: number;
|
|
521
|
+
tags: string[];
|
|
522
522
|
entityRef?: string | null | undefined;
|
|
523
523
|
structuredAttributes?: Record<string, string> | null | undefined;
|
|
524
524
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -544,10 +544,10 @@ declare const ProactiveExtractionResultSchema: z.ZodObject<{
|
|
|
544
544
|
observed_outcome?: string | null | undefined;
|
|
545
545
|
} | null | undefined;
|
|
546
546
|
}, {
|
|
547
|
-
tags: string[];
|
|
548
|
-
content: string;
|
|
549
547
|
category: "fact" | "preference" | "correction" | "entity" | "decision" | "relationship" | "principle" | "commitment" | "moment" | "skill" | "rule" | "procedure" | "reasoning_trace";
|
|
548
|
+
content: string;
|
|
550
549
|
confidence: number;
|
|
550
|
+
tags: string[];
|
|
551
551
|
entityRef?: string | null | undefined;
|
|
552
552
|
structuredAttributes?: Record<string, string> | null | undefined;
|
|
553
553
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -631,10 +631,10 @@ declare const ProactiveExtractionResultSchema: z.ZodObject<{
|
|
|
631
631
|
}>, "many">>>;
|
|
632
632
|
}, "strip", z.ZodTypeAny, {
|
|
633
633
|
facts: {
|
|
634
|
-
tags: string[];
|
|
635
|
-
content: string;
|
|
636
634
|
category: "fact" | "preference" | "correction" | "entity" | "decision" | "relationship" | "principle" | "commitment" | "moment" | "skill" | "rule" | "procedure" | "reasoning_trace";
|
|
635
|
+
content: string;
|
|
637
636
|
confidence: number;
|
|
637
|
+
tags: string[];
|
|
638
638
|
entityRef?: string | null | undefined;
|
|
639
639
|
structuredAttributes?: Record<string, string> | null | undefined;
|
|
640
640
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -680,10 +680,10 @@ declare const ProactiveExtractionResultSchema: z.ZodObject<{
|
|
|
680
680
|
}[] | null | undefined;
|
|
681
681
|
}, {
|
|
682
682
|
facts: {
|
|
683
|
-
tags: string[];
|
|
684
|
-
content: string;
|
|
685
683
|
category: "fact" | "preference" | "correction" | "entity" | "decision" | "relationship" | "principle" | "commitment" | "moment" | "skill" | "rule" | "procedure" | "reasoning_trace";
|
|
684
|
+
content: string;
|
|
686
685
|
confidence: number;
|
|
686
|
+
tags: string[];
|
|
687
687
|
entityRef?: string | null | undefined;
|
|
688
688
|
structuredAttributes?: Record<string, string> | null | undefined;
|
|
689
689
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -825,10 +825,10 @@ declare const ExtractionResultSchema: z.ZodObject<{
|
|
|
825
825
|
observed_outcome?: string | null | undefined;
|
|
826
826
|
}>>>;
|
|
827
827
|
}, "strip", z.ZodTypeAny, {
|
|
828
|
-
tags: string[];
|
|
829
|
-
content: string;
|
|
830
828
|
category: "fact" | "preference" | "correction" | "entity" | "decision" | "relationship" | "principle" | "commitment" | "moment" | "skill" | "rule" | "procedure" | "reasoning_trace";
|
|
829
|
+
content: string;
|
|
831
830
|
confidence: number;
|
|
831
|
+
tags: string[];
|
|
832
832
|
entityRef?: string | null | undefined;
|
|
833
833
|
structuredAttributes?: Record<string, string> | null | undefined;
|
|
834
834
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -854,10 +854,10 @@ declare const ExtractionResultSchema: z.ZodObject<{
|
|
|
854
854
|
observed_outcome?: string | null | undefined;
|
|
855
855
|
} | null | undefined;
|
|
856
856
|
}, {
|
|
857
|
-
tags: string[];
|
|
858
|
-
content: string;
|
|
859
857
|
category: "fact" | "preference" | "correction" | "entity" | "decision" | "relationship" | "principle" | "commitment" | "moment" | "skill" | "rule" | "procedure" | "reasoning_trace";
|
|
858
|
+
content: string;
|
|
860
859
|
confidence: number;
|
|
860
|
+
tags: string[];
|
|
861
861
|
entityRef?: string | null | undefined;
|
|
862
862
|
structuredAttributes?: Record<string, string> | null | undefined;
|
|
863
863
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -883,10 +883,10 @@ declare const ExtractionResultSchema: z.ZodObject<{
|
|
|
883
883
|
observed_outcome?: string | null | undefined;
|
|
884
884
|
} | null | undefined;
|
|
885
885
|
}>, {
|
|
886
|
-
tags: string[];
|
|
887
|
-
content: string;
|
|
888
886
|
category: "fact" | "preference" | "correction" | "entity" | "decision" | "relationship" | "principle" | "commitment" | "moment" | "skill" | "rule" | "procedure" | "reasoning_trace";
|
|
887
|
+
content: string;
|
|
889
888
|
confidence: number;
|
|
889
|
+
tags: string[];
|
|
890
890
|
entityRef?: string | null | undefined;
|
|
891
891
|
structuredAttributes?: Record<string, string> | null | undefined;
|
|
892
892
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -912,10 +912,10 @@ declare const ExtractionResultSchema: z.ZodObject<{
|
|
|
912
912
|
observed_outcome?: string | null | undefined;
|
|
913
913
|
} | null | undefined;
|
|
914
914
|
}, {
|
|
915
|
-
tags: string[];
|
|
916
|
-
content: string;
|
|
917
915
|
category: "fact" | "preference" | "correction" | "entity" | "decision" | "relationship" | "principle" | "commitment" | "moment" | "skill" | "rule" | "procedure" | "reasoning_trace";
|
|
916
|
+
content: string;
|
|
918
917
|
confidence: number;
|
|
918
|
+
tags: string[];
|
|
919
919
|
entityRef?: string | null | undefined;
|
|
920
920
|
structuredAttributes?: Record<string, string> | null | undefined;
|
|
921
921
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -1013,10 +1013,10 @@ declare const ExtractionResultSchema: z.ZodObject<{
|
|
|
1013
1013
|
}>, "many">>>;
|
|
1014
1014
|
}, "strip", z.ZodTypeAny, {
|
|
1015
1015
|
facts: {
|
|
1016
|
-
tags: string[];
|
|
1017
|
-
content: string;
|
|
1018
1016
|
category: "fact" | "preference" | "correction" | "entity" | "decision" | "relationship" | "principle" | "commitment" | "moment" | "skill" | "rule" | "procedure" | "reasoning_trace";
|
|
1017
|
+
content: string;
|
|
1019
1018
|
confidence: number;
|
|
1019
|
+
tags: string[];
|
|
1020
1020
|
entityRef?: string | null | undefined;
|
|
1021
1021
|
structuredAttributes?: Record<string, string> | null | undefined;
|
|
1022
1022
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -1068,10 +1068,10 @@ declare const ExtractionResultSchema: z.ZodObject<{
|
|
|
1068
1068
|
identityReflection?: string | null | undefined;
|
|
1069
1069
|
}, {
|
|
1070
1070
|
facts: {
|
|
1071
|
-
tags: string[];
|
|
1072
|
-
content: string;
|
|
1073
1071
|
category: "fact" | "preference" | "correction" | "entity" | "decision" | "relationship" | "principle" | "commitment" | "moment" | "skill" | "rule" | "procedure" | "reasoning_trace";
|
|
1072
|
+
content: string;
|
|
1074
1073
|
confidence: number;
|
|
1074
|
+
tags: string[];
|
|
1075
1075
|
entityRef?: string | null | undefined;
|
|
1076
1076
|
structuredAttributes?: Record<string, string> | null | undefined;
|
|
1077
1077
|
promptedByQuestion?: string | null | undefined;
|
|
@@ -1285,13 +1285,13 @@ declare const ContradictionVerificationSchema: z.ZodObject<{
|
|
|
1285
1285
|
reasoning: z.ZodString;
|
|
1286
1286
|
whichIsNewer: z.ZodEnum<["first", "second", "unclear"]>;
|
|
1287
1287
|
}, "strip", z.ZodTypeAny, {
|
|
1288
|
-
reasoning: string;
|
|
1289
1288
|
confidence: number;
|
|
1289
|
+
reasoning: string;
|
|
1290
1290
|
isContradiction: boolean;
|
|
1291
1291
|
whichIsNewer: "first" | "second" | "unclear";
|
|
1292
1292
|
}, {
|
|
1293
|
-
reasoning: string;
|
|
1294
1293
|
confidence: number;
|
|
1294
|
+
reasoning: string;
|
|
1295
1295
|
isContradiction: boolean;
|
|
1296
1296
|
whichIsNewer: "first" | "second" | "unclear";
|
|
1297
1297
|
}>;
|
|
@@ -1386,8 +1386,8 @@ declare const BehaviorLoopAdjustmentSchema: z.ZodObject<{
|
|
|
1386
1386
|
reason: z.ZodString;
|
|
1387
1387
|
appliedAt: z.ZodString;
|
|
1388
1388
|
}, "strip", z.ZodTypeAny, {
|
|
1389
|
-
reason: string;
|
|
1390
1389
|
confidence: number;
|
|
1390
|
+
reason: string;
|
|
1391
1391
|
parameter: string;
|
|
1392
1392
|
previousValue: number;
|
|
1393
1393
|
nextValue: number;
|
|
@@ -1395,8 +1395,8 @@ declare const BehaviorLoopAdjustmentSchema: z.ZodObject<{
|
|
|
1395
1395
|
evidenceCount: number;
|
|
1396
1396
|
appliedAt: string;
|
|
1397
1397
|
}, {
|
|
1398
|
-
reason: string;
|
|
1399
1398
|
confidence: number;
|
|
1399
|
+
reason: string;
|
|
1400
1400
|
parameter: string;
|
|
1401
1401
|
previousValue: number;
|
|
1402
1402
|
nextValue: number;
|
|
@@ -1420,8 +1420,8 @@ declare const BehaviorLoopPolicyStateSchema: z.ZodObject<{
|
|
|
1420
1420
|
reason: z.ZodString;
|
|
1421
1421
|
appliedAt: z.ZodString;
|
|
1422
1422
|
}, "strip", z.ZodTypeAny, {
|
|
1423
|
-
reason: string;
|
|
1424
1423
|
confidence: number;
|
|
1424
|
+
reason: string;
|
|
1425
1425
|
parameter: string;
|
|
1426
1426
|
previousValue: number;
|
|
1427
1427
|
nextValue: number;
|
|
@@ -1429,8 +1429,8 @@ declare const BehaviorLoopPolicyStateSchema: z.ZodObject<{
|
|
|
1429
1429
|
evidenceCount: number;
|
|
1430
1430
|
appliedAt: string;
|
|
1431
1431
|
}, {
|
|
1432
|
-
reason: string;
|
|
1433
1432
|
confidence: number;
|
|
1433
|
+
reason: string;
|
|
1434
1434
|
parameter: string;
|
|
1435
1435
|
previousValue: number;
|
|
1436
1436
|
nextValue: number;
|
|
@@ -1447,8 +1447,8 @@ declare const BehaviorLoopPolicyStateSchema: z.ZodObject<{
|
|
|
1447
1447
|
maxDeltaPerCycle: number;
|
|
1448
1448
|
protectedParams: string[];
|
|
1449
1449
|
adjustments: {
|
|
1450
|
-
reason: string;
|
|
1451
1450
|
confidence: number;
|
|
1451
|
+
reason: string;
|
|
1452
1452
|
parameter: string;
|
|
1453
1453
|
previousValue: number;
|
|
1454
1454
|
nextValue: number;
|
|
@@ -1464,8 +1464,8 @@ declare const BehaviorLoopPolicyStateSchema: z.ZodObject<{
|
|
|
1464
1464
|
maxDeltaPerCycle: number;
|
|
1465
1465
|
protectedParams: string[];
|
|
1466
1466
|
adjustments: {
|
|
1467
|
-
reason: string;
|
|
1468
1467
|
confidence: number;
|
|
1468
|
+
reason: string;
|
|
1469
1469
|
parameter: string;
|
|
1470
1470
|
previousValue: number;
|
|
1471
1471
|
nextValue: number;
|
|
@@ -22,9 +22,9 @@ declare const SharedFeedbackEntrySchema: z.ZodObject<{
|
|
|
22
22
|
agent: string;
|
|
23
23
|
date: string;
|
|
24
24
|
reason: string;
|
|
25
|
+
confidence?: number | undefined;
|
|
25
26
|
workflow?: string | undefined;
|
|
26
27
|
tags?: string[] | undefined;
|
|
27
|
-
confidence?: number | undefined;
|
|
28
28
|
severity?: "low" | "medium" | "high" | undefined;
|
|
29
29
|
outcome?: string | undefined;
|
|
30
30
|
refs?: string[] | undefined;
|
|
@@ -36,9 +36,9 @@ declare const SharedFeedbackEntrySchema: z.ZodObject<{
|
|
|
36
36
|
agent: string;
|
|
37
37
|
date: string;
|
|
38
38
|
reason: string;
|
|
39
|
+
confidence?: number | undefined;
|
|
39
40
|
workflow?: string | undefined;
|
|
40
41
|
tags?: string[] | undefined;
|
|
41
|
-
confidence?: number | undefined;
|
|
42
42
|
severity?: "low" | "medium" | "high" | undefined;
|
|
43
43
|
outcome?: string | undefined;
|
|
44
44
|
refs?: string[] | undefined;
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* so swapping storage providers requires no pipeline changes.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import
|
|
9
|
+
import type { Stats } from "node:fs";
|
|
10
10
|
import fsp from "node:fs/promises";
|
|
11
11
|
import path from "node:path";
|
|
12
12
|
import type { BinaryStorageBackendConfig } from "./types.js";
|
|
@@ -27,6 +27,8 @@ export interface BinaryStorageBackend {
|
|
|
27
27
|
exists(remotePath: string): Promise<boolean>;
|
|
28
28
|
/** Delete a file from the backend. */
|
|
29
29
|
delete(remotePath: string): Promise<void>;
|
|
30
|
+
/** Return the user-resolvable markdown target for a stored backend path. */
|
|
31
|
+
getRedirectTarget?(remotePath: string): string;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
// ---------------------------------------------------------------------------
|
|
@@ -45,10 +47,9 @@ export class FilesystemBackend implements BinaryStorageBackend {
|
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
private resolveRemotePath(remotePath: string): string {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const resolved = path.resolve(this.basePath, remotePath);
|
|
50
|
+
const resolved = path.isAbsolute(remotePath)
|
|
51
|
+
? path.resolve(remotePath)
|
|
52
|
+
: path.resolve(this.basePath, remotePath);
|
|
52
53
|
const relative = path.relative(this.basePath, resolved);
|
|
53
54
|
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
54
55
|
throw new Error(`FilesystemBackend remotePath escapes basePath: ${JSON.stringify(remotePath)}`);
|
|
@@ -56,26 +57,169 @@ export class FilesystemBackend implements BinaryStorageBackend {
|
|
|
56
57
|
return resolved;
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
private isInsideBase(candidate: string, realBase: string): boolean {
|
|
61
|
+
const relative = path.relative(realBase, candidate);
|
|
62
|
+
return relative === "" || (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private async realBasePathIfExists(): Promise<string | null> {
|
|
66
|
+
try {
|
|
67
|
+
const stat = await fsp.lstat(this.basePath);
|
|
68
|
+
if (stat.isSymbolicLink()) {
|
|
69
|
+
throw new Error(`FilesystemBackend basePath must not be a symlink: ${this.basePath}`);
|
|
70
|
+
}
|
|
71
|
+
if (!stat.isDirectory()) {
|
|
72
|
+
throw new Error(`FilesystemBackend basePath must be a directory: ${this.basePath}`);
|
|
73
|
+
}
|
|
74
|
+
return await fsp.realpath(this.basePath);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private async ensureBaseDirectory(): Promise<string> {
|
|
84
|
+
await fsp.mkdir(this.basePath, { recursive: true });
|
|
85
|
+
const realBase = await this.realBasePathIfExists();
|
|
86
|
+
if (realBase === null) {
|
|
87
|
+
throw new Error(`FilesystemBackend failed to create basePath: ${this.basePath}`);
|
|
88
|
+
}
|
|
89
|
+
return realBase;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private async ensureSafeParentDirectory(dest: string): Promise<string> {
|
|
93
|
+
const realBase = await this.ensureBaseDirectory();
|
|
94
|
+
const destDir = path.dirname(dest);
|
|
95
|
+
const relativeDir = path.relative(this.basePath, destDir);
|
|
96
|
+
const segments = relativeDir === "" ? [] : relativeDir.split(path.sep);
|
|
97
|
+
let current = this.basePath;
|
|
98
|
+
|
|
99
|
+
for (const segment of segments) {
|
|
100
|
+
if (segment === "." || segment === "") continue;
|
|
101
|
+
current = path.join(current, segment);
|
|
102
|
+
try {
|
|
103
|
+
const stat = await fsp.lstat(current);
|
|
104
|
+
if (stat.isSymbolicLink()) {
|
|
105
|
+
throw new Error(`FilesystemBackend remotePath traverses symlink: ${current}`);
|
|
106
|
+
}
|
|
107
|
+
if (!stat.isDirectory()) {
|
|
108
|
+
throw new Error(`FilesystemBackend remotePath parent is not a directory: ${current}`);
|
|
109
|
+
}
|
|
110
|
+
} catch (err) {
|
|
111
|
+
if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
112
|
+
throw err;
|
|
113
|
+
}
|
|
114
|
+
await fsp.mkdir(current);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const realParent = await fsp.realpath(destDir);
|
|
119
|
+
if (!this.isInsideBase(realParent, realBase)) {
|
|
120
|
+
throw new Error(`FilesystemBackend remotePath parent escapes basePath: ${dest}`);
|
|
121
|
+
}
|
|
122
|
+
return realBase;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private async resolveExistingRemotePath(remotePath: string): Promise<string | null> {
|
|
60
126
|
const dest = this.resolveRemotePath(remotePath);
|
|
127
|
+
const realBase = await this.realBasePathIfExists();
|
|
128
|
+
if (realBase === null) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
61
132
|
const destDir = path.dirname(dest);
|
|
62
|
-
|
|
63
|
-
|
|
133
|
+
const relativeDir = path.relative(this.basePath, destDir);
|
|
134
|
+
const segments = relativeDir === "" ? [] : relativeDir.split(path.sep);
|
|
135
|
+
let current = this.basePath;
|
|
136
|
+
for (const segment of segments) {
|
|
137
|
+
if (segment === "." || segment === "") continue;
|
|
138
|
+
current = path.join(current, segment);
|
|
139
|
+
let stat: Stats;
|
|
140
|
+
try {
|
|
141
|
+
stat = await fsp.lstat(current);
|
|
142
|
+
} catch (err) {
|
|
143
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
throw err;
|
|
147
|
+
}
|
|
148
|
+
if (stat.isSymbolicLink()) {
|
|
149
|
+
throw new Error(`FilesystemBackend remotePath traverses symlink: ${current}`);
|
|
150
|
+
}
|
|
151
|
+
if (!stat.isDirectory()) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const realParent = await fsp.realpath(destDir).catch((err: NodeJS.ErrnoException) => {
|
|
157
|
+
if (err.code === "ENOENT") return null;
|
|
158
|
+
throw err;
|
|
159
|
+
});
|
|
160
|
+
if (realParent === null) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
if (!this.isInsideBase(realParent, realBase)) {
|
|
164
|
+
throw new Error(`FilesystemBackend remotePath parent escapes basePath: ${JSON.stringify(remotePath)}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const stat = await fsp.lstat(dest);
|
|
169
|
+
if (stat.isSymbolicLink()) {
|
|
170
|
+
throw new Error(`FilesystemBackend remotePath points to symlink: ${dest}`);
|
|
171
|
+
}
|
|
172
|
+
if (!stat.isFile()) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
} catch (err) {
|
|
176
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
throw err;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const realDest = await fsp.realpath(dest);
|
|
183
|
+
if (!this.isInsideBase(realDest, realBase)) {
|
|
184
|
+
throw new Error(`FilesystemBackend remotePath escapes basePath: ${JSON.stringify(remotePath)}`);
|
|
185
|
+
}
|
|
64
186
|
return dest;
|
|
65
187
|
}
|
|
66
188
|
|
|
67
|
-
async
|
|
189
|
+
async upload(localPath: string, remotePath: string): Promise<string> {
|
|
190
|
+
if (path.isAbsolute(remotePath)) {
|
|
191
|
+
throw new Error(`FilesystemBackend upload remotePath must be relative: ${JSON.stringify(remotePath)}`);
|
|
192
|
+
}
|
|
68
193
|
const dest = this.resolveRemotePath(remotePath);
|
|
194
|
+
const realBase = await this.ensureSafeParentDirectory(dest);
|
|
69
195
|
try {
|
|
70
|
-
await fsp.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
196
|
+
const stat = await fsp.lstat(dest);
|
|
197
|
+
if (stat.isSymbolicLink()) {
|
|
198
|
+
throw new Error(`FilesystemBackend remotePath points to symlink: ${dest}`);
|
|
199
|
+
}
|
|
200
|
+
} catch (err) {
|
|
201
|
+
if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
202
|
+
throw err;
|
|
203
|
+
}
|
|
74
204
|
}
|
|
205
|
+
await fsp.copyFile(localPath, dest);
|
|
206
|
+
const realDest = await fsp.realpath(dest);
|
|
207
|
+
if (!this.isInsideBase(realDest, realBase)) {
|
|
208
|
+
throw new Error(`FilesystemBackend remotePath escapes basePath: ${JSON.stringify(remotePath)}`);
|
|
209
|
+
}
|
|
210
|
+
return remotePath;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async exists(remotePath: string): Promise<boolean> {
|
|
214
|
+
const dest = await this.resolveExistingRemotePath(remotePath);
|
|
215
|
+
return dest !== null;
|
|
75
216
|
}
|
|
76
217
|
|
|
77
218
|
async delete(remotePath: string): Promise<void> {
|
|
78
|
-
const dest = this.
|
|
219
|
+
const dest = await this.resolveExistingRemotePath(remotePath);
|
|
220
|
+
if (dest === null) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
79
223
|
try {
|
|
80
224
|
await fsp.unlink(dest);
|
|
81
225
|
} catch (err: unknown) {
|
|
@@ -83,6 +227,10 @@ export class FilesystemBackend implements BinaryStorageBackend {
|
|
|
83
227
|
if ((err as NodeJS.ErrnoException).code !== "ENOENT") throw err;
|
|
84
228
|
}
|
|
85
229
|
}
|
|
230
|
+
|
|
231
|
+
getRedirectTarget(remotePath: string): string {
|
|
232
|
+
return this.resolveRemotePath(remotePath);
|
|
233
|
+
}
|
|
86
234
|
}
|
|
87
235
|
|
|
88
236
|
// ---------------------------------------------------------------------------
|
|
@@ -24,25 +24,37 @@ export function manifestPath(memoryDir: string): string {
|
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Read the manifest from disk. Returns a fresh empty manifest if the file
|
|
27
|
-
* does not exist
|
|
27
|
+
* does not exist. Existing invalid manifests fail closed so the pipeline does
|
|
28
|
+
* not overwrite state needed for safe cleanup.
|
|
28
29
|
*/
|
|
29
30
|
export async function readManifest(memoryDir: string): Promise<BinaryLifecycleManifest> {
|
|
30
31
|
const filePath = manifestPath(memoryDir);
|
|
32
|
+
let raw: string;
|
|
31
33
|
try {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
34
|
+
raw = await fsp.readFile(filePath, "utf-8");
|
|
35
|
+
} catch (err) {
|
|
36
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
36
37
|
return emptyManifest();
|
|
37
38
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
throw new Error(`Failed to read binary lifecycle manifest at ${filePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let parsed: unknown;
|
|
43
|
+
try {
|
|
44
|
+
parsed = JSON.parse(raw);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
throw new Error(`Invalid binary lifecycle manifest JSON at ${filePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// CLAUDE.md #18: validate the parsed result is a non-null object.
|
|
50
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
51
|
+
throw new Error(`Invalid binary lifecycle manifest shape at ${filePath}: expected object`);
|
|
52
|
+
}
|
|
53
|
+
const obj = parsed as Record<string, unknown>;
|
|
54
|
+
if (obj.version !== 1 || !Array.isArray(obj.assets)) {
|
|
55
|
+
throw new Error(`Invalid binary lifecycle manifest shape at ${filePath}: expected version 1 with assets array`);
|
|
45
56
|
}
|
|
57
|
+
return parsed as BinaryLifecycleManifest;
|
|
46
58
|
}
|
|
47
59
|
|
|
48
60
|
/**
|