@remnic/core 9.3.600 → 9.3.602
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-cli.js +6 -6
- package/dist/access-http.js +6 -6
- package/dist/access-mcp.js +5 -5
- package/dist/access-service.js +4 -4
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.js +2 -2
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory-graph.js +1 -1
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-ELKI4BB6.js → chunk-2ESBDLT5.js} +3 -3
- package/dist/{chunk-WZA5Y6AC.js → chunk-2QANQKSQ.js} +3 -3
- package/dist/{chunk-BDCCWRHR.js → chunk-5RPTH6AU.js} +3 -3
- package/dist/{chunk-JKV57BTN.js → chunk-7V2SGZ3H.js} +2 -2
- package/dist/{chunk-D4KJ74JJ.js → chunk-EWC6W6AB.js} +2 -2
- package/dist/{chunk-V67GWXM2.js → chunk-IP73YCZP.js} +1 -1
- package/dist/{chunk-KDUVQU6Y.js → chunk-MTGOAU7A.js} +4 -4
- package/dist/{chunk-65JSA4MP.js → chunk-RUYYYLDT.js} +7 -7
- package/dist/{chunk-CL3MWNNQ.js → chunk-TH67Q46T.js} +3 -3
- package/dist/{chunk-ZZYF3BUL.js → chunk-TQOU3VAT.js} +1 -1
- package/dist/{chunk-4JRRISLU.js → chunk-XL7FK7PJ.js} +61 -43
- package/dist/chunk-XL7FK7PJ.js.map +1 -0
- package/dist/cli.js +8 -8
- package/dist/compounding/engine.js +1 -1
- package/dist/{graph-edge-decay-MUP5J7CC.js → graph-edge-decay-5ZOK7ZNO.js} +2 -2
- package/dist/graph-snapshot.js +2 -2
- package/dist/graph.js +1 -1
- package/dist/index.js +403 -116
- package/dist/index.js.map +1 -1
- package/dist/operator-toolkit.js +2 -2
- package/dist/orchestrator.js +4 -4
- package/package.json +1 -1
- package/src/graph.test.ts +76 -11
- package/src/graph.ts +101 -88
- package/src/spaces/index.test.ts +205 -18
- package/src/spaces/index.ts +453 -139
- package/dist/chunk-4JRRISLU.js.map +0 -1
- /package/dist/{chunk-ELKI4BB6.js.map → chunk-2ESBDLT5.js.map} +0 -0
- /package/dist/{chunk-WZA5Y6AC.js.map → chunk-2QANQKSQ.js.map} +0 -0
- /package/dist/{chunk-BDCCWRHR.js.map → chunk-5RPTH6AU.js.map} +0 -0
- /package/dist/{chunk-JKV57BTN.js.map → chunk-7V2SGZ3H.js.map} +0 -0
- /package/dist/{chunk-D4KJ74JJ.js.map → chunk-EWC6W6AB.js.map} +0 -0
- /package/dist/{chunk-V67GWXM2.js.map → chunk-IP73YCZP.js.map} +0 -0
- /package/dist/{chunk-KDUVQU6Y.js.map → chunk-MTGOAU7A.js.map} +0 -0
- /package/dist/{chunk-65JSA4MP.js.map → chunk-RUYYYLDT.js.map} +0 -0
- /package/dist/{chunk-CL3MWNNQ.js.map → chunk-TH67Q46T.js.map} +0 -0
- /package/dist/{chunk-ZZYF3BUL.js.map → chunk-TQOU3VAT.js.map} +0 -0
- /package/dist/{graph-edge-decay-MUP5J7CC.js.map → graph-edge-decay-5ZOK7ZNO.js.map} +0 -0
package/src/spaces/index.test.ts
CHANGED
|
@@ -1,11 +1,32 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import {
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import fs, { existsSync } from "node:fs";
|
|
4
|
+
import { mkdir, mkdtemp, rm, utimes, writeFile } from "node:fs/promises";
|
|
3
5
|
import os from "node:os";
|
|
4
6
|
import path from "node:path";
|
|
5
7
|
import test from "node:test";
|
|
8
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
6
10
|
|
|
7
11
|
import { createSpace, loadManifest } from "./index.js";
|
|
8
12
|
|
|
13
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const repoRoot = path.resolve(__dirname, "../../../..");
|
|
15
|
+
const tsxCli = path.join(repoRoot, "node_modules", "tsx", "dist", "cli.mjs");
|
|
16
|
+
const spacesSourcePath = path.join(__dirname, "index.ts");
|
|
17
|
+
|
|
18
|
+
function unsetEnv(name: string): void {
|
|
19
|
+
Reflect.deleteProperty(process.env, name);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function restoreEnv(name: string, value: string | undefined): void {
|
|
23
|
+
if (value === undefined) {
|
|
24
|
+
unsetEnv(name);
|
|
25
|
+
} else {
|
|
26
|
+
process.env[name] = value;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
9
30
|
test("personal space bootstrap prefers REMNIC_MEMORY_DIR over legacy ENGRAM_MEMORY_DIR", async () => {
|
|
10
31
|
const baseDir = await mkdtemp(path.join(os.tmpdir(), "remnic-spaces-current-"));
|
|
11
32
|
const remnicMemoryDir = path.join(baseDir, "remnic-memory");
|
|
@@ -19,10 +40,8 @@ test("personal space bootstrap prefers REMNIC_MEMORY_DIR over legacy ENGRAM_MEMO
|
|
|
19
40
|
const manifest = loadManifest(baseDir);
|
|
20
41
|
assert.equal(manifest.spaces[0]?.memoryDir, remnicMemoryDir);
|
|
21
42
|
} finally {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (previousEngram === undefined) delete process.env.ENGRAM_MEMORY_DIR;
|
|
25
|
-
else process.env.ENGRAM_MEMORY_DIR = previousEngram;
|
|
43
|
+
restoreEnv("REMNIC_MEMORY_DIR", previousRemnic);
|
|
44
|
+
restoreEnv("ENGRAM_MEMORY_DIR", previousEngram);
|
|
26
45
|
await rm(baseDir, { recursive: true, force: true });
|
|
27
46
|
}
|
|
28
47
|
});
|
|
@@ -33,16 +52,14 @@ test("personal space bootstrap keeps ENGRAM_MEMORY_DIR as a legacy fallback", as
|
|
|
33
52
|
const previousRemnic = process.env.REMNIC_MEMORY_DIR;
|
|
34
53
|
const previousEngram = process.env.ENGRAM_MEMORY_DIR;
|
|
35
54
|
|
|
36
|
-
|
|
55
|
+
unsetEnv("REMNIC_MEMORY_DIR");
|
|
37
56
|
process.env.ENGRAM_MEMORY_DIR = legacyMemoryDir;
|
|
38
57
|
try {
|
|
39
58
|
const manifest = loadManifest(baseDir);
|
|
40
59
|
assert.equal(manifest.spaces[0]?.memoryDir, legacyMemoryDir);
|
|
41
60
|
} finally {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (previousEngram === undefined) delete process.env.ENGRAM_MEMORY_DIR;
|
|
45
|
-
else process.env.ENGRAM_MEMORY_DIR = previousEngram;
|
|
61
|
+
restoreEnv("REMNIC_MEMORY_DIR", previousRemnic);
|
|
62
|
+
restoreEnv("ENGRAM_MEMORY_DIR", previousEngram);
|
|
46
63
|
await rm(baseDir, { recursive: true, force: true });
|
|
47
64
|
}
|
|
48
65
|
});
|
|
@@ -56,8 +73,7 @@ test("personal space bootstrap normalizes relative memoryDir env values", async
|
|
|
56
73
|
const manifest = loadManifest(baseDir);
|
|
57
74
|
assert.equal(manifest.spaces[0]?.memoryDir, path.resolve("relative-personal-memory"));
|
|
58
75
|
} finally {
|
|
59
|
-
|
|
60
|
-
else process.env.REMNIC_MEMORY_DIR = previousRemnic;
|
|
76
|
+
restoreEnv("REMNIC_MEMORY_DIR", previousRemnic);
|
|
61
77
|
await rm(baseDir, { recursive: true, force: true });
|
|
62
78
|
}
|
|
63
79
|
});
|
|
@@ -68,8 +84,8 @@ test("createSpace normalizes caller-provided memoryDir before saving manifest",
|
|
|
68
84
|
const previousRemnic = process.env.REMNIC_MEMORY_DIR;
|
|
69
85
|
const previousEngram = process.env.ENGRAM_MEMORY_DIR;
|
|
70
86
|
|
|
71
|
-
|
|
72
|
-
|
|
87
|
+
unsetEnv("REMNIC_MEMORY_DIR");
|
|
88
|
+
unsetEnv("ENGRAM_MEMORY_DIR");
|
|
73
89
|
try {
|
|
74
90
|
const created = createSpace({
|
|
75
91
|
baseDir,
|
|
@@ -83,11 +99,182 @@ test("createSpace normalizes caller-provided memoryDir before saving manifest",
|
|
|
83
99
|
const saved = manifest.spaces.find((space) => space.id === "project");
|
|
84
100
|
assert.equal(saved?.memoryDir, projectMemoryDir);
|
|
85
101
|
} finally {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (previousEngram === undefined) delete process.env.ENGRAM_MEMORY_DIR;
|
|
89
|
-
else process.env.ENGRAM_MEMORY_DIR = previousEngram;
|
|
102
|
+
restoreEnv("REMNIC_MEMORY_DIR", previousRemnic);
|
|
103
|
+
restoreEnv("ENGRAM_MEMORY_DIR", previousEngram);
|
|
90
104
|
await rm(baseDir, { recursive: true, force: true });
|
|
91
105
|
await rm(projectMemoryDir, { recursive: true, force: true });
|
|
92
106
|
}
|
|
93
107
|
});
|
|
108
|
+
|
|
109
|
+
test("concurrent createSpace calls preserve both manifest updates", async () => {
|
|
110
|
+
const baseDir = await mkdtemp(path.join(os.tmpdir(), "remnic-spaces-concurrent-"));
|
|
111
|
+
const childScript = path.join(baseDir, "create-space-child.mjs");
|
|
112
|
+
const goPath = path.join(baseDir, "go");
|
|
113
|
+
const manifestPath = path.join(baseDir, ".config", "engram", "spaces", "manifest.json");
|
|
114
|
+
|
|
115
|
+
await writeFile(
|
|
116
|
+
childScript,
|
|
117
|
+
`
|
|
118
|
+
import fs from "node:fs";
|
|
119
|
+
import path from "node:path";
|
|
120
|
+
import { pathToFileURL } from "node:url";
|
|
121
|
+
|
|
122
|
+
const [baseDir, name, manifestPath, goPath, spacesSourcePath] = process.argv.slice(2);
|
|
123
|
+
const readyPath = path.join(baseDir, \`\${name}.ready\`);
|
|
124
|
+
const originalReadFileSync = fs.readFileSync.bind(fs);
|
|
125
|
+
const sleepBuffer = new Int32Array(new SharedArrayBuffer(4));
|
|
126
|
+
let paused = false;
|
|
127
|
+
|
|
128
|
+
fs.readFileSync = function patchedReadFileSync(filePath, ...args) {
|
|
129
|
+
const result = originalReadFileSync(filePath, ...args);
|
|
130
|
+
if (!paused && path.resolve(String(filePath)) === manifestPath) {
|
|
131
|
+
paused = true;
|
|
132
|
+
fs.writeFileSync(readyPath, "ready\\n");
|
|
133
|
+
const deadline = Date.now() + 10_000;
|
|
134
|
+
while (!fs.existsSync(goPath)) {
|
|
135
|
+
if (Date.now() > deadline) {
|
|
136
|
+
throw new Error(\`Timed out waiting for manifest race release: \${goPath}\`);
|
|
137
|
+
}
|
|
138
|
+
Atomics.wait(sleepBuffer, 0, 0, 20);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return result;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const { createSpace } = await import(pathToFileURL(spacesSourcePath).href);
|
|
145
|
+
createSpace({ baseDir, name, kind: "project" });
|
|
146
|
+
`,
|
|
147
|
+
"utf8"
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
loadManifest(baseDir);
|
|
152
|
+
|
|
153
|
+
const first = runSpaceChild(baseDir, "Alpha", manifestPath, goPath, childScript);
|
|
154
|
+
const second = runSpaceChild(baseDir, "Beta", manifestPath, goPath, childScript);
|
|
155
|
+
|
|
156
|
+
await waitForAnyFile([path.join(baseDir, "Alpha.ready"), path.join(baseDir, "Beta.ready")]);
|
|
157
|
+
await sleep(300);
|
|
158
|
+
await writeFile(goPath, "go\n", "utf8");
|
|
159
|
+
await Promise.all([first, second]);
|
|
160
|
+
|
|
161
|
+
const manifest = loadManifest(baseDir);
|
|
162
|
+
const ids = manifest.spaces.map((space) => space.id).sort();
|
|
163
|
+
assert.ok(ids.includes("alpha"), `expected alpha in ${ids.join(", ")}`);
|
|
164
|
+
assert.ok(ids.includes("beta"), `expected beta in ${ids.join(", ")}`);
|
|
165
|
+
} finally {
|
|
166
|
+
await rm(baseDir, { recursive: true, force: true });
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("createSpace reclaims stale manifest locks only after the owner exits", async () => {
|
|
171
|
+
const baseDir = await mkdtemp(path.join(os.tmpdir(), "remnic-spaces-stale-lock-"));
|
|
172
|
+
const manifestPath = path.join(baseDir, ".config", "engram", "spaces", "manifest.json");
|
|
173
|
+
const lockDir = `${manifestPath}.lock`;
|
|
174
|
+
const staleTime = new Date(Date.now() - 31_000);
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
loadManifest(baseDir);
|
|
178
|
+
await mkdir(lockDir);
|
|
179
|
+
await writeFile(path.join(lockDir, "owner"), "999999:stale-owner\n", "utf8");
|
|
180
|
+
await utimes(lockDir, staleTime, staleTime);
|
|
181
|
+
|
|
182
|
+
createSpace({ baseDir, name: "Recovered", kind: "project" });
|
|
183
|
+
|
|
184
|
+
const manifest = loadManifest(baseDir);
|
|
185
|
+
assert.ok(manifest.spaces.some((space) => space.id === "recovered"));
|
|
186
|
+
assert.equal(existsSync(lockDir), false);
|
|
187
|
+
assert.equal(existsSync(`${lockDir}.reclaim`), false);
|
|
188
|
+
} finally {
|
|
189
|
+
await rm(baseDir, { recursive: true, force: true });
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("createSpace reclaims stale manifest reclaim locks", async () => {
|
|
194
|
+
const baseDir = await mkdtemp(path.join(os.tmpdir(), "remnic-spaces-stale-reclaim-lock-"));
|
|
195
|
+
const manifestPath = path.join(baseDir, ".config", "engram", "spaces", "manifest.json");
|
|
196
|
+
const lockDir = `${manifestPath}.lock`;
|
|
197
|
+
const reclaimDir = `${lockDir}.reclaim`;
|
|
198
|
+
const staleTime = new Date(Date.now() - 31_000);
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
loadManifest(baseDir);
|
|
202
|
+
await mkdir(reclaimDir);
|
|
203
|
+
await utimes(reclaimDir, staleTime, staleTime);
|
|
204
|
+
|
|
205
|
+
createSpace({ baseDir, name: "Recovered", kind: "project" });
|
|
206
|
+
|
|
207
|
+
const manifest = loadManifest(baseDir);
|
|
208
|
+
assert.ok(manifest.spaces.some((space) => space.id === "recovered"));
|
|
209
|
+
assert.equal(existsSync(reclaimDir), false);
|
|
210
|
+
} finally {
|
|
211
|
+
await rm(baseDir, { recursive: true, force: true });
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("loadManifest persists bootstrap when manifest disappears between existence check and read", async () => {
|
|
216
|
+
const baseDir = await mkdtemp(path.join(os.tmpdir(), "remnic-spaces-vanishing-manifest-"));
|
|
217
|
+
const manifestPath = path.join(baseDir, ".config", "engram", "spaces", "manifest.json");
|
|
218
|
+
const originalExistsSync = fs.existsSync.bind(fs);
|
|
219
|
+
let forceInitialExists = true;
|
|
220
|
+
|
|
221
|
+
fs.existsSync = ((filePath) => {
|
|
222
|
+
if (forceInitialExists && path.resolve(String(filePath)) === manifestPath) {
|
|
223
|
+
forceInitialExists = false;
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
return originalExistsSync(filePath);
|
|
227
|
+
}) as typeof fs.existsSync;
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
const manifest = loadManifest(baseDir);
|
|
231
|
+
|
|
232
|
+
assert.equal(manifest.activeSpaceId, "personal");
|
|
233
|
+
assert.equal(manifest.spaces[0]?.id, "personal");
|
|
234
|
+
assert.equal(originalExistsSync(manifestPath), true);
|
|
235
|
+
} finally {
|
|
236
|
+
fs.existsSync = originalExistsSync;
|
|
237
|
+
await rm(baseDir, { recursive: true, force: true });
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
async function runSpaceChild(
|
|
242
|
+
baseDir: string,
|
|
243
|
+
name: string,
|
|
244
|
+
manifestPath: string,
|
|
245
|
+
goPath: string,
|
|
246
|
+
childScript: string
|
|
247
|
+
): Promise<void> {
|
|
248
|
+
const child = spawn(process.execPath, [tsxCli, childScript, baseDir, name, manifestPath, goPath, spacesSourcePath], {
|
|
249
|
+
cwd: repoRoot,
|
|
250
|
+
env: process.env,
|
|
251
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
let stdout = "";
|
|
255
|
+
let stderr = "";
|
|
256
|
+
child.stdout.setEncoding("utf8");
|
|
257
|
+
child.stderr.setEncoding("utf8");
|
|
258
|
+
child.stdout.on("data", (chunk) => {
|
|
259
|
+
stdout += chunk;
|
|
260
|
+
});
|
|
261
|
+
child.stderr.on("data", (chunk) => {
|
|
262
|
+
stderr += chunk;
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
const code = await new Promise<number | null>((resolve, reject) => {
|
|
266
|
+
child.on("error", reject);
|
|
267
|
+
child.on("close", resolve);
|
|
268
|
+
});
|
|
269
|
+
assert.equal(code, 0, `child ${name} failed\nstdout:\n${stdout}\nstderr:\n${stderr}`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function waitForAnyFile(filePaths: string[]): Promise<void> {
|
|
273
|
+
const deadline = Date.now() + 5_000;
|
|
274
|
+
while (!filePaths.some((filePath) => existsSync(filePath))) {
|
|
275
|
+
if (Date.now() > deadline) {
|
|
276
|
+
throw new Error(`Timed out waiting for one of: ${filePaths.join(", ")}`);
|
|
277
|
+
}
|
|
278
|
+
await sleep(20);
|
|
279
|
+
}
|
|
280
|
+
}
|