@remnic/core 1.1.21 → 1.1.23

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.
Files changed (103) hide show
  1. package/dist/access-cli.js +15 -15
  2. package/dist/access-http.d.ts +9 -1
  3. package/dist/access-http.js +9 -9
  4. package/dist/access-mcp.d.ts +1 -1
  5. package/dist/access-mcp.js +8 -8
  6. package/dist/access-schema.js +3 -3
  7. package/dist/{access-service-DT9L2DW4.d.ts → access-service-CEyV8XJ5.d.ts} +19 -2
  8. package/dist/access-service.d.ts +1 -1
  9. package/dist/access-service.js +6 -6
  10. package/dist/briefing.js +3 -3
  11. package/dist/causal-consolidation.js +4 -4
  12. package/dist/{chunk-YO3AZEE5.js → chunk-25YQM6XW.js} +3 -3
  13. package/dist/{chunk-TLM762GT.js → chunk-2WIPXV3Y.js} +2 -2
  14. package/dist/{chunk-QOHBYVZG.js → chunk-3F24QTRI.js} +2 -2
  15. package/dist/{chunk-5IQC4OG6.js → chunk-4H6DURG6.js} +2 -2
  16. package/dist/{chunk-NOQ74SJN.js → chunk-7D6O46PF.js} +2 -2
  17. package/dist/{chunk-SLKSC522.js → chunk-7E7SZRPP.js} +2 -2
  18. package/dist/{chunk-7Q2P774N.js → chunk-F33CJ5CH.js} +13 -3
  19. package/dist/chunk-F33CJ5CH.js.map +1 -0
  20. package/dist/{chunk-APW7AQOJ.js → chunk-FHXVW3L4.js} +4 -4
  21. package/dist/{chunk-PFFKUJM2.js → chunk-HWF42K6J.js} +103 -4
  22. package/dist/chunk-HWF42K6J.js.map +1 -0
  23. package/dist/{chunk-FSODDMR2.js → chunk-IANK6Y5W.js} +2 -2
  24. package/dist/{chunk-BYYIIXIJ.js → chunk-JKXFF3NT.js} +361 -29
  25. package/dist/chunk-JKXFF3NT.js.map +1 -0
  26. package/dist/{chunk-P7F6DJPA.js → chunk-MM5EBZVW.js} +42 -5
  27. package/dist/chunk-MM5EBZVW.js.map +1 -0
  28. package/dist/{chunk-GGCJ253V.js → chunk-MVAOT247.js} +8 -8
  29. package/dist/{chunk-SH5S7XYD.js → chunk-MXFBBHJU.js} +72 -2
  30. package/dist/chunk-MXFBBHJU.js.map +1 -0
  31. package/dist/{chunk-SZKCBLS5.js → chunk-PUXCIHRL.js} +2 -2
  32. package/dist/{chunk-2IRT26RZ.js → chunk-QYHQ2JHL.js} +2 -2
  33. package/dist/{chunk-73DAPA62.js → chunk-RA73CTVY.js} +12 -12
  34. package/dist/{chunk-CN4P6SVA.js → chunk-RCZRL5BE.js} +2 -2
  35. package/dist/{chunk-SGIXDVSF.js → chunk-S27EXIHY.js} +2 -2
  36. package/dist/{chunk-5ML4TH3E.js → chunk-TFORLO3O.js} +4 -4
  37. package/dist/{chunk-TOFUTKQN.js → chunk-TR4DK5OH.js} +2 -2
  38. package/dist/{chunk-6ORWKANA.js → chunk-VYU7PXUS.js} +2 -2
  39. package/dist/{chunk-FFU4GMST.js → chunk-WNARATI3.js} +2 -2
  40. package/dist/{chunk-KSFBM6TV.js → chunk-YITUHONZ.js} +2 -2
  41. package/dist/{cli-BN0CkYzI.d.ts → cli-BguVmIwO.d.ts} +1 -1
  42. package/dist/cli.d.ts +2 -2
  43. package/dist/cli.js +18 -18
  44. package/dist/compounding/engine.js +3 -3
  45. package/dist/connectors/codex-materialize-runner.js +3 -3
  46. package/dist/connectors/index.js +3 -3
  47. package/dist/entity-retrieval.js +3 -3
  48. package/dist/index.d.ts +4 -4
  49. package/dist/index.js +26 -24
  50. package/dist/index.js.map +1 -1
  51. package/dist/maintenance/memory-governance.js +3 -3
  52. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
  53. package/dist/maintenance/rebuild-memory-projection.js +4 -4
  54. package/dist/mcp-memory-inspector-app.d.ts +1 -1
  55. package/dist/namespaces/migrate.js +4 -4
  56. package/dist/namespaces/storage.js +3 -3
  57. package/dist/offline-sync.d.ts +36 -1
  58. package/dist/offline-sync.js +4 -2
  59. package/dist/operator-toolkit.js +6 -6
  60. package/dist/orchestrator.js +11 -11
  61. package/dist/schemas.d.ts +22 -22
  62. package/dist/secure-store/index.js +2 -2
  63. package/dist/semantic-consolidation.js +4 -4
  64. package/dist/semantic-rule-promotion.js +3 -3
  65. package/dist/semantic-rule-verifier.js +3 -3
  66. package/dist/storage.d.ts +2 -0
  67. package/dist/storage.js +2 -2
  68. package/dist/transfer/types.d.ts +12 -12
  69. package/dist/verified-recall.js +3 -3
  70. package/package.json +1 -1
  71. package/src/access-http.test.ts +176 -0
  72. package/src/access-http.ts +116 -0
  73. package/src/access-service-offline-file-content.test.ts +37 -0
  74. package/src/access-service.ts +70 -0
  75. package/src/index.ts +2 -0
  76. package/src/offline-sync.test.ts +448 -64
  77. package/src/offline-sync.ts +477 -29
  78. package/src/secure-store/secure-fs.ts +84 -3
  79. package/src/storage.ts +12 -0
  80. package/dist/chunk-7Q2P774N.js.map +0 -1
  81. package/dist/chunk-BYYIIXIJ.js.map +0 -1
  82. package/dist/chunk-P7F6DJPA.js.map +0 -1
  83. package/dist/chunk-PFFKUJM2.js.map +0 -1
  84. package/dist/chunk-SH5S7XYD.js.map +0 -1
  85. /package/dist/{chunk-YO3AZEE5.js.map → chunk-25YQM6XW.js.map} +0 -0
  86. /package/dist/{chunk-TLM762GT.js.map → chunk-2WIPXV3Y.js.map} +0 -0
  87. /package/dist/{chunk-QOHBYVZG.js.map → chunk-3F24QTRI.js.map} +0 -0
  88. /package/dist/{chunk-5IQC4OG6.js.map → chunk-4H6DURG6.js.map} +0 -0
  89. /package/dist/{chunk-NOQ74SJN.js.map → chunk-7D6O46PF.js.map} +0 -0
  90. /package/dist/{chunk-SLKSC522.js.map → chunk-7E7SZRPP.js.map} +0 -0
  91. /package/dist/{chunk-APW7AQOJ.js.map → chunk-FHXVW3L4.js.map} +0 -0
  92. /package/dist/{chunk-FSODDMR2.js.map → chunk-IANK6Y5W.js.map} +0 -0
  93. /package/dist/{chunk-GGCJ253V.js.map → chunk-MVAOT247.js.map} +0 -0
  94. /package/dist/{chunk-SZKCBLS5.js.map → chunk-PUXCIHRL.js.map} +0 -0
  95. /package/dist/{chunk-2IRT26RZ.js.map → chunk-QYHQ2JHL.js.map} +0 -0
  96. /package/dist/{chunk-73DAPA62.js.map → chunk-RA73CTVY.js.map} +0 -0
  97. /package/dist/{chunk-CN4P6SVA.js.map → chunk-RCZRL5BE.js.map} +0 -0
  98. /package/dist/{chunk-SGIXDVSF.js.map → chunk-S27EXIHY.js.map} +0 -0
  99. /package/dist/{chunk-5ML4TH3E.js.map → chunk-TFORLO3O.js.map} +0 -0
  100. /package/dist/{chunk-TOFUTKQN.js.map → chunk-TR4DK5OH.js.map} +0 -0
  101. /package/dist/{chunk-6ORWKANA.js.map → chunk-VYU7PXUS.js.map} +0 -0
  102. /package/dist/{chunk-FFU4GMST.js.map → chunk-WNARATI3.js.map} +0 -0
  103. /package/dist/{chunk-KSFBM6TV.js.map → chunk-YITUHONZ.js.map} +0 -0
@@ -1,11 +1,12 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { createHash } from "node:crypto";
3
- import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
+ import { mkdir, mkdtemp, readdir, readFile, rm, utimes, writeFile } from "node:fs/promises";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
6
  import test from "node:test";
7
7
 
8
8
  import {
9
+ applyOfflineSyncFileContentChunk,
9
10
  applyOfflineSyncChangeset,
10
11
  applyOfflineSyncSnapshot,
11
12
  buildOfflineSyncChangeset,
@@ -58,7 +59,21 @@ test("offline snapshot captures source-of-truth files and excludes private/inter
58
59
 
59
60
  assert.deepEqual(
60
61
  snapshot.files.map((file) => file.path),
61
- ["assets/blob.bin", "facts/a.md", "facts/fact-hashes.txt", "transcripts/session.jsonl"],
62
+ [
63
+ "assets/blob.bin",
64
+ "facts/a.md",
65
+ "facts/fact-hashes.txt",
66
+ "state/fact-hashes.ready",
67
+ "state/fact-hashes.txt",
68
+ "state/last_graph_recall.json",
69
+ "state/last_intent.json",
70
+ "state/last_qmd_recall.json",
71
+ "state/last_recall.json",
72
+ "state/lcm.sqlite",
73
+ "state/lcm.sqlite-shm",
74
+ "state/lcm.sqlite-wal",
75
+ "transcripts/session.jsonl",
76
+ ],
62
77
  );
63
78
  const binary = snapshot.files.find((file) => file.path === "assets/blob.bin");
64
79
  assert.equal(Buffer.from(binary?.contentBase64 ?? "", "base64")[3], 255);
@@ -71,14 +86,27 @@ test("offline snapshot captures source-of-truth files and excludes private/inter
71
86
  });
72
87
  assert.deepEqual(
73
88
  withoutTranscripts.files.map((file) => file.path),
74
- ["assets/blob.bin", "facts/a.md", "facts/fact-hashes.txt"],
89
+ [
90
+ "assets/blob.bin",
91
+ "facts/a.md",
92
+ "facts/fact-hashes.txt",
93
+ "state/fact-hashes.ready",
94
+ "state/fact-hashes.txt",
95
+ "state/last_graph_recall.json",
96
+ "state/last_intent.json",
97
+ "state/last_qmd_recall.json",
98
+ "state/last_recall.json",
99
+ "state/lcm.sqlite",
100
+ "state/lcm.sqlite-shm",
101
+ "state/lcm.sqlite-wal",
102
+ ],
75
103
  );
76
104
  } finally {
77
105
  await rm(root, { recursive: true, force: true });
78
106
  }
79
107
  });
80
108
 
81
- test("offline sync excludes volatile retrieval debug snapshots without deleting existing local copies", async () => {
109
+ test("offline sync includes retrieval debug snapshots for full-fidelity offline recall", async () => {
82
110
  const root = await tempDir("remnic-offline-debug-snapshots");
83
111
  try {
84
112
  await write(root, "facts/a.md", "alpha");
@@ -93,25 +121,25 @@ test("offline sync excludes volatile retrieval debug snapshots without deleting
93
121
  includeContent: true,
94
122
  });
95
123
 
96
- assert.deepEqual(snapshot.files.map((file) => file.path), ["facts/a.md"]);
97
- await assert.rejects(
98
- () =>
99
- buildOfflineSyncSnapshotForPaths({
100
- root,
101
- sourceId: "remote",
102
- paths: ["state/last_graph_recall.json"],
103
- includeContent: true,
104
- }),
105
- /offline sync snapshot path is excluded: state\/last_graph_recall\.json/,
106
- );
107
- await assert.rejects(
108
- () =>
109
- readOfflineSyncFileContentChunk({
110
- root,
111
- path: "state/last_graph_recall.json",
112
- }),
113
- /offline sync file content path is excluded: state\/last_graph_recall\.json/,
114
- );
124
+ assert.deepEqual(snapshot.files.map((file) => file.path), [
125
+ "facts/a.md",
126
+ "state/last_graph_recall.json",
127
+ "state/last_intent.json",
128
+ "state/last_qmd_recall.json",
129
+ "state/last_recall.json",
130
+ ]);
131
+ const focused = await buildOfflineSyncSnapshotForPaths({
132
+ root,
133
+ sourceId: "remote",
134
+ paths: ["state/last_graph_recall.json"],
135
+ includeContent: true,
136
+ });
137
+ assert.deepEqual(focused.files.map((file) => file.path), ["state/last_graph_recall.json"]);
138
+ const chunk = await readOfflineSyncFileContentChunk({
139
+ root,
140
+ path: "state/last_graph_recall.json",
141
+ });
142
+ assert.equal(chunk.content.toString("utf-8"), "graph");
115
143
 
116
144
  const oldGraph = Buffer.from("old graph");
117
145
  const pull = await applyOfflineSyncSnapshot({
@@ -125,14 +153,14 @@ test("offline sync excludes volatile retrieval debug snapshots without deleting
125
153
  }],
126
154
  });
127
155
 
128
- assert.equal(pull.deleted, 0);
156
+ assert.equal(pull.skipped, 5);
129
157
  assert.equal(await readUtf8(root, "state/last_graph_recall.json"), "graph");
130
158
  } finally {
131
159
  await rm(root, { recursive: true, force: true });
132
160
  }
133
161
  });
134
162
 
135
- test("offline sync excludes live LCM sqlite artifacts without deleting existing local copies", async () => {
163
+ test("offline sync includes live LCM sqlite artifacts for full-fidelity offline mode", async () => {
136
164
  const root = await tempDir("remnic-offline-lcm-sqlite");
137
165
  try {
138
166
  await write(root, "facts/a.md", "alpha");
@@ -146,17 +174,19 @@ test("offline sync excludes live LCM sqlite artifacts without deleting existing
146
174
  includeContent: true,
147
175
  });
148
176
 
149
- assert.deepEqual(snapshot.files.map((file) => file.path), ["facts/a.md"]);
150
- await assert.rejects(
151
- () =>
152
- buildOfflineSyncSnapshotForPaths({
153
- root,
154
- sourceId: "remote",
155
- paths: ["state/lcm.sqlite"],
156
- includeContent: true,
157
- }),
158
- /offline sync snapshot path is excluded: state\/lcm\.sqlite/,
159
- );
177
+ assert.deepEqual(snapshot.files.map((file) => file.path), [
178
+ "facts/a.md",
179
+ "state/lcm.sqlite",
180
+ "state/lcm.sqlite-shm",
181
+ "state/lcm.sqlite-wal",
182
+ ]);
183
+ const focused = await buildOfflineSyncSnapshotForPaths({
184
+ root,
185
+ sourceId: "remote",
186
+ paths: ["state/lcm.sqlite"],
187
+ includeContent: true,
188
+ });
189
+ assert.deepEqual(focused.files.map((file) => file.path), ["state/lcm.sqlite"]);
160
190
 
161
191
  const oldDb = Buffer.from("old live db");
162
192
  const pull = await applyOfflineSyncSnapshot({
@@ -170,28 +200,35 @@ test("offline sync excludes live LCM sqlite artifacts without deleting existing
170
200
  }],
171
201
  });
172
202
 
173
- assert.equal(pull.deleted, 0);
203
+ assert.equal(pull.skipped, 4);
174
204
  assert.equal(await readUtf8(root, "state/lcm.sqlite"), "live db");
175
205
  } finally {
176
206
  await rm(root, { recursive: true, force: true });
177
207
  }
178
208
  });
179
209
 
180
- test("offline sync excludes runtime-derived state without deleting existing local copies", async () => {
210
+ test("offline sync includes durable runtime state and excludes only transient sync temp files", async () => {
181
211
  const root = await tempDir("remnic-offline-runtime-state");
182
212
  try {
183
213
  await write(root, "facts/a.md", "alpha");
214
+ await write(root, "assets/state/fact-hashes.txt", "durable asset");
184
215
  await write(root, "state/buffer-surprise-ledger.jsonl", "surprise");
185
216
  await write(root, "state/buffer.json", "buffer");
186
217
  await write(root, "state/buffer.json.tmp-123-456", "tmp");
187
218
  await write(root, "state/embeddings.json", "embeddings");
219
+ await write(root, "state/entity-mention-index.json", "entities");
188
220
  await write(root, "state/index_tags.json", "tags");
189
221
  await write(root, "state/index_time.json", "time");
190
222
  await write(root, "state/memory-lifecycle-ledger.jsonl", "ledger");
223
+ await write(root, "state/.artifact-write-version.log", "version");
224
+ await write(root, "state/.memory-status-version.log", "version");
191
225
  await write(root, "state/memory-projection.sqlite", "projection");
192
226
  await write(root, "state/memory-projection.sqlite-shm", "projection-shm");
193
227
  await write(root, "state/memory-projection.sqlite-wal", "projection-wal");
194
228
  await write(root, "state/recall_impressions.jsonl", "impressions");
229
+ await write(root, "namespaces/generalist-project-origin-6ebeaa54/state/last_intent.json", "intent");
230
+ await write(root, "namespaces/generalist-project-origin-6ebeaa54/state/entity-mention-index.json", "entities");
231
+ await write(root, "namespaces/generalist-project-origin-6ebeaa54/state/.memory-status-version.log", "version");
195
232
 
196
233
  const snapshot = await buildOfflineSyncSnapshot({
197
234
  root,
@@ -199,17 +236,33 @@ test("offline sync excludes runtime-derived state without deleting existing loca
199
236
  includeContent: true,
200
237
  });
201
238
 
202
- assert.deepEqual(snapshot.files.map((file) => file.path), ["facts/a.md"]);
203
- await assert.rejects(
204
- () =>
205
- buildOfflineSyncSnapshotForPaths({
206
- root,
207
- sourceId: "remote",
208
- paths: ["state/memory-lifecycle-ledger.jsonl"],
209
- includeContent: true,
210
- }),
211
- /offline sync snapshot path is excluded: state\/memory-lifecycle-ledger\.jsonl/,
212
- );
239
+ assert.deepEqual(snapshot.files.map((file) => file.path), [
240
+ "assets/state/fact-hashes.txt",
241
+ "facts/a.md",
242
+ "namespaces/generalist-project-origin-6ebeaa54/state/.memory-status-version.log",
243
+ "namespaces/generalist-project-origin-6ebeaa54/state/entity-mention-index.json",
244
+ "namespaces/generalist-project-origin-6ebeaa54/state/last_intent.json",
245
+ "state/.artifact-write-version.log",
246
+ "state/.memory-status-version.log",
247
+ "state/buffer-surprise-ledger.jsonl",
248
+ "state/buffer.json",
249
+ "state/embeddings.json",
250
+ "state/entity-mention-index.json",
251
+ "state/index_tags.json",
252
+ "state/index_time.json",
253
+ "state/memory-lifecycle-ledger.jsonl",
254
+ "state/memory-projection.sqlite",
255
+ "state/memory-projection.sqlite-shm",
256
+ "state/memory-projection.sqlite-wal",
257
+ "state/recall_impressions.jsonl",
258
+ ]);
259
+ const focused = await buildOfflineSyncSnapshotForPaths({
260
+ root,
261
+ sourceId: "remote",
262
+ paths: ["state/memory-lifecycle-ledger.jsonl"],
263
+ includeContent: true,
264
+ });
265
+ assert.deepEqual(focused.files.map((file) => file.path), ["state/memory-lifecycle-ledger.jsonl"]);
213
266
  await assert.rejects(
214
267
  () =>
215
268
  readOfflineSyncFileContentChunk({
@@ -218,6 +271,15 @@ test("offline sync excludes runtime-derived state without deleting existing loca
218
271
  }),
219
272
  /offline sync file content path is excluded: state\/buffer\.json\.tmp-123-456/,
220
273
  );
274
+ const namespaced = await buildOfflineSyncSnapshotForPaths({
275
+ root,
276
+ sourceId: "remote",
277
+ paths: ["namespaces/generalist-project-origin-6ebeaa54/state/last_intent.json"],
278
+ includeContent: true,
279
+ });
280
+ assert.deepEqual(namespaced.files.map((file) => file.path), [
281
+ "namespaces/generalist-project-origin-6ebeaa54/state/last_intent.json",
282
+ ]);
221
283
 
222
284
  const oldLedger = Buffer.from("old ledger");
223
285
  const pull = await applyOfflineSyncSnapshot({
@@ -231,20 +293,22 @@ test("offline sync excludes runtime-derived state without deleting existing loca
231
293
  }],
232
294
  });
233
295
 
234
- assert.equal(pull.deleted, 0);
296
+ assert.equal(pull.skipped, 18);
235
297
  assert.equal(await readUtf8(root, "state/memory-lifecycle-ledger.jsonl"), "ledger");
236
298
  } finally {
237
299
  await rm(root, { recursive: true, force: true });
238
300
  }
239
301
  });
240
302
 
241
- test("offline sync ignores runtime-derived records from older peers", async () => {
303
+ test("offline sync accepts durable runtime records from older peers", async () => {
242
304
  const root = await tempDir("remnic-offline-legacy-runtime-state");
243
305
  try {
244
306
  const fact = Buffer.from("alpha");
245
307
  const runtime = Buffer.from("legacy runtime");
308
+ const asset = Buffer.from("durable asset");
246
309
  const runtimeSha = createHash("sha256").update(runtime).digest("hex");
247
310
  const factSha = createHash("sha256").update(fact).digest("hex");
311
+ const assetSha = createHash("sha256").update(asset).digest("hex");
248
312
 
249
313
  const pull = await applyOfflineSyncSnapshot({
250
314
  root,
@@ -262,6 +326,20 @@ test("offline sync ignores runtime-derived records from older peers", async () =
262
326
  mtimeMs: 0,
263
327
  contentBase64: runtime.toString("base64"),
264
328
  },
329
+ {
330
+ path: "state/buffer.json.tmp-123-456",
331
+ sha256: runtimeSha,
332
+ bytes: runtime.byteLength,
333
+ mtimeMs: 0,
334
+ contentBase64: runtime.toString("base64"),
335
+ },
336
+ {
337
+ path: "namespaces/generalist-project-origin-6ebeaa54/state/last_intent.json",
338
+ sha256: runtimeSha,
339
+ bytes: runtime.byteLength,
340
+ mtimeMs: 0,
341
+ contentBase64: runtime.toString("base64"),
342
+ },
265
343
  {
266
344
  path: "facts/a.md",
267
345
  sha256: factSha,
@@ -269,14 +347,27 @@ test("offline sync ignores runtime-derived records from older peers", async () =
269
347
  mtimeMs: 0,
270
348
  contentBase64: fact.toString("base64"),
271
349
  },
350
+ {
351
+ path: "assets/state/fact-hashes.txt",
352
+ sha256: assetSha,
353
+ bytes: asset.byteLength,
354
+ mtimeMs: 0,
355
+ contentBase64: asset.toString("base64"),
356
+ },
272
357
  ],
273
358
  },
274
359
  });
275
360
 
276
- assert.equal(pull.upserted, 1);
361
+ assert.equal(pull.upserted, 4);
277
362
  assert.equal(await readUtf8(root, "facts/a.md"), "alpha");
363
+ assert.equal(await readUtf8(root, "assets/state/fact-hashes.txt"), "durable asset");
364
+ assert.equal(await readUtf8(root, "state/buffer.json"), "legacy runtime");
365
+ assert.equal(
366
+ await readUtf8(root, "namespaces/generalist-project-origin-6ebeaa54/state/last_intent.json"),
367
+ "legacy runtime",
368
+ );
278
369
  await assert.rejects(
279
- () => readFile(path.join(root, "state", "buffer.json")),
370
+ () => readFile(path.join(root, "state", "buffer.json.tmp-123-456")),
280
371
  /ENOENT/,
281
372
  );
282
373
 
@@ -302,6 +393,28 @@ test("offline sync ignores runtime-derived records from older peers", async () =
302
393
  contentBase64: runtime.toString("base64"),
303
394
  },
304
395
  },
396
+ {
397
+ type: "upsert",
398
+ path: "state/buffer.json.tmp-123-456",
399
+ file: {
400
+ path: "state/buffer.json.tmp-123-456",
401
+ sha256: runtimeSha,
402
+ bytes: runtime.byteLength,
403
+ mtimeMs: 0,
404
+ contentBase64: runtime.toString("base64"),
405
+ },
406
+ },
407
+ {
408
+ type: "upsert",
409
+ path: "namespaces/generalist-project-origin-6ebeaa54/state/last_intent.json",
410
+ file: {
411
+ path: "namespaces/generalist-project-origin-6ebeaa54/state/last_intent.json",
412
+ sha256: runtimeSha,
413
+ bytes: runtime.byteLength,
414
+ mtimeMs: 0,
415
+ contentBase64: runtime.toString("base64"),
416
+ },
417
+ },
305
418
  {
306
419
  type: "upsert",
307
420
  path: "facts/a.md",
@@ -313,14 +426,34 @@ test("offline sync ignores runtime-derived records from older peers", async () =
313
426
  contentBase64: fact.toString("base64"),
314
427
  },
315
428
  },
429
+ {
430
+ type: "upsert",
431
+ path: "assets/state/fact-hashes.txt",
432
+ file: {
433
+ path: "assets/state/fact-hashes.txt",
434
+ sha256: assetSha,
435
+ bytes: asset.byteLength,
436
+ mtimeMs: 0,
437
+ contentBase64: asset.toString("base64"),
438
+ },
439
+ },
316
440
  ],
317
441
  },
318
442
  });
319
443
 
320
- assert.equal(push.appliedUpserts, 1);
444
+ assert.equal(push.appliedUpserts, 4);
321
445
  assert.equal(await readUtf8(remote, "facts/a.md"), "alpha");
446
+ assert.equal(await readUtf8(remote, "assets/state/fact-hashes.txt"), "durable asset");
447
+ assert.equal(
448
+ await readUtf8(remote, "state/memory-lifecycle-ledger.jsonl"),
449
+ "legacy runtime",
450
+ );
451
+ assert.equal(
452
+ await readUtf8(remote, "namespaces/generalist-project-origin-6ebeaa54/state/last_intent.json"),
453
+ "legacy runtime",
454
+ );
322
455
  await assert.rejects(
323
- () => readFile(path.join(remote, "state", "memory-lifecycle-ledger.jsonl")),
456
+ () => readFile(path.join(remote, "state", "buffer.json.tmp-123-456")),
324
457
  /ENOENT/,
325
458
  );
326
459
  } finally {
@@ -358,6 +491,7 @@ test("offline sync reads bounded file content chunks with metadata", async () =>
358
491
  const root = await tempDir("remnic-offline-file-content");
359
492
  try {
360
493
  await write(root, "artifacts/large.txt", "alpha\nbeta\ngamma\n");
494
+ await write(root, "state/lcm.sqlite", "live db");
361
495
 
362
496
  const chunk = await readOfflineSyncFileContentChunk({
363
497
  root,
@@ -372,14 +506,199 @@ test("offline sync reads bounded file content chunks with metadata", async () =>
372
506
  assert.equal(chunk.content.toString("utf-8"), "beta\n");
373
507
  assert.equal(chunk.bytes, Buffer.byteLength("alpha\nbeta\ngamma\n"));
374
508
 
375
- await assert.rejects(
376
- () =>
377
- readOfflineSyncFileContentChunk({
378
- root,
379
- path: "state/lcm.sqlite",
380
- }),
381
- /offline sync file content path is excluded: state\/lcm\.sqlite/,
382
- );
509
+ const lcm = await readOfflineSyncFileContentChunk({
510
+ root,
511
+ path: "state/lcm.sqlite",
512
+ });
513
+ assert.equal(lcm.content.toString("utf-8"), "live db");
514
+ } finally {
515
+ await rm(root, { recursive: true, force: true });
516
+ }
517
+ });
518
+
519
+ test("offline sync applies chunked file content with base conflict checks", async () => {
520
+ const root = await tempDir("remnic-offline-file-content-apply");
521
+ try {
522
+ await write(root, "state/lcm.sqlite", "old");
523
+ const oldSha = createHash("sha256").update("old").digest("hex");
524
+ const next = Buffer.from("new durable sqlite content");
525
+ const nextSha = createHash("sha256").update(next).digest("hex");
526
+
527
+ const first = await applyOfflineSyncFileContentChunk({
528
+ root,
529
+ sourceId: "laptop",
530
+ path: "state/lcm.sqlite",
531
+ sha256: nextSha,
532
+ bytes: next.byteLength,
533
+ mtimeMs: 123,
534
+ offset: 0,
535
+ baseSha256: oldSha,
536
+ content: next.subarray(0, 8),
537
+ });
538
+ assert.equal(first.done, false);
539
+
540
+ const second = await applyOfflineSyncFileContentChunk({
541
+ root,
542
+ sourceId: "laptop",
543
+ path: "state/lcm.sqlite",
544
+ sha256: nextSha,
545
+ bytes: next.byteLength,
546
+ mtimeMs: 123,
547
+ offset: 8,
548
+ baseSha256: oldSha,
549
+ content: next.subarray(8),
550
+ });
551
+ assert.equal(second.done, true);
552
+ assert.equal(second.applied, true);
553
+ assert.equal(await readUtf8(root, "state/lcm.sqlite"), "new durable sqlite content");
554
+
555
+ const conflictContent = Buffer.from("conflicting local sqlite");
556
+ const conflictSha = createHash("sha256").update(conflictContent).digest("hex");
557
+ const conflict = await applyOfflineSyncFileContentChunk({
558
+ root,
559
+ sourceId: "laptop",
560
+ path: "state/lcm.sqlite",
561
+ sha256: conflictSha,
562
+ bytes: conflictContent.byteLength,
563
+ mtimeMs: 456,
564
+ offset: 0,
565
+ baseSha256: oldSha,
566
+ content: conflictContent,
567
+ });
568
+ assert.equal(conflict.done, true);
569
+ assert.equal(conflict.applied, false);
570
+ assert.equal(conflict.conflict?.reason, "remote_changed_for_local_update");
571
+ assert.equal(await readUtf8(root, "state/lcm.sqlite"), "new durable sqlite content");
572
+ } finally {
573
+ await rm(root, { recursive: true, force: true });
574
+ }
575
+ });
576
+
577
+ test("offline sync stages chunked uploads through storage hooks", async () => {
578
+ const root = await tempDir("remnic-offline-file-content-hooks");
579
+ const encode = (content: Buffer) => Buffer.from(`ENC:${content.toString("base64")}`);
580
+ const decode = (content: Buffer) => {
581
+ const text = content.toString("utf-8");
582
+ return text.startsWith("ENC:") ? Buffer.from(text.slice(4), "base64") : content;
583
+ };
584
+ const readHook = async ({ filePath }: { filePath: string }) => decode(await readFile(filePath));
585
+ let stagingWrites = 0;
586
+ let mutationWrites = 0;
587
+ const writeStagingHook = async ({ filePath, content }: { filePath: string; content: Buffer }) => {
588
+ stagingWrites += 1;
589
+ await mkdir(path.dirname(filePath), { recursive: true });
590
+ await writeFile(filePath, encode(content));
591
+ };
592
+ const writeHook = async ({ filePath, content }: { filePath: string; content: Buffer }) => {
593
+ mutationWrites += 1;
594
+ await mkdir(path.dirname(filePath), { recursive: true });
595
+ await writeFile(filePath, encode(content));
596
+ };
597
+ const writeChunksHook = async ({
598
+ filePath,
599
+ chunks,
600
+ }: {
601
+ filePath: string;
602
+ chunks: AsyncIterable<Buffer>;
603
+ }) => {
604
+ const content: Buffer[] = [];
605
+ for await (const chunk of chunks) content.push(chunk);
606
+ await writeHook({ filePath, content: Buffer.concat(content) });
607
+ };
608
+
609
+ try {
610
+ await write(root, "state/lcm.sqlite", "old");
611
+ const oldSha = createHash("sha256").update("old").digest("hex");
612
+ const next = Buffer.from("new durable sqlite content");
613
+ const nextSha = createHash("sha256").update(next).digest("hex");
614
+
615
+ const first = await applyOfflineSyncFileContentChunk({
616
+ root,
617
+ sourceId: "laptop",
618
+ path: "state/lcm.sqlite",
619
+ sha256: nextSha,
620
+ bytes: next.byteLength,
621
+ mtimeMs: 123,
622
+ offset: 0,
623
+ baseSha256: oldSha,
624
+ content: next.subarray(0, 8),
625
+ readFile: readHook,
626
+ writeFile: writeHook,
627
+ writeStagingFile: writeStagingHook,
628
+ writeFileChunks: writeChunksHook,
629
+ });
630
+ assert.equal(first.done, false);
631
+ assert.equal(stagingWrites, 1);
632
+ assert.equal(mutationWrites, 0);
633
+ const uploadEntries = await readdir(path.join(root, ".offline-sync", "uploads"));
634
+ assert.equal(uploadEntries.length, 1);
635
+ const uploadChunkEntries = await readdir(path.join(root, ".offline-sync", "uploads", uploadEntries[0]));
636
+ assert.deepEqual(uploadChunkEntries, ["00000000000000000000.part"]);
637
+ const rawUpload = await readFile(path.join(
638
+ root,
639
+ ".offline-sync",
640
+ "uploads",
641
+ uploadEntries[0],
642
+ uploadChunkEntries[0],
643
+ ));
644
+ assert.match(rawUpload.toString("utf-8"), /^ENC:/);
645
+ assert.equal(rawUpload.includes(next.subarray(0, 8)), false);
646
+
647
+ const second = await applyOfflineSyncFileContentChunk({
648
+ root,
649
+ sourceId: "laptop",
650
+ path: "state/lcm.sqlite",
651
+ sha256: nextSha,
652
+ bytes: next.byteLength,
653
+ mtimeMs: 123,
654
+ offset: 8,
655
+ baseSha256: oldSha,
656
+ content: next.subarray(8),
657
+ readFile: readHook,
658
+ writeFile: writeHook,
659
+ writeStagingFile: writeStagingHook,
660
+ writeFileChunks: writeChunksHook,
661
+ });
662
+ assert.equal(second.applied, true);
663
+ assert.equal(stagingWrites, 2);
664
+ assert.equal(mutationWrites, 1);
665
+ assert.equal((await readdir(path.join(root, ".offline-sync", "uploads"))).length, 0);
666
+ const rawTarget = await readFile(path.join(root, "state/lcm.sqlite"));
667
+ assert.match(rawTarget.toString("utf-8"), /^ENC:/);
668
+ assert.equal(decode(rawTarget).toString("utf-8"), "new durable sqlite content");
669
+ } finally {
670
+ await rm(root, { recursive: true, force: true });
671
+ }
672
+ });
673
+
674
+ test("offline sync prunes stale staged uploads when starting a new upload", async () => {
675
+ const root = await tempDir("remnic-offline-file-content-prune");
676
+ try {
677
+ const staleKey = `${"a".repeat(64)}.part`;
678
+ const staleDir = path.join(root, ".offline-sync", "uploads", staleKey);
679
+ await mkdir(staleDir, { recursive: true });
680
+ await writeFile(path.join(staleDir, "00000000000000000000.part"), "abandoned");
681
+ const staleTime = new Date(Date.now() - 25 * 60 * 60 * 1000);
682
+ await utimes(path.join(staleDir, "00000000000000000000.part"), staleTime, staleTime);
683
+ await utimes(staleDir, staleTime, staleTime);
684
+
685
+ const next = Buffer.from("new durable sqlite content");
686
+ const nextSha = createHash("sha256").update(next).digest("hex");
687
+ const first = await applyOfflineSyncFileContentChunk({
688
+ root,
689
+ sourceId: "laptop",
690
+ path: "state/lcm.sqlite",
691
+ sha256: nextSha,
692
+ bytes: next.byteLength,
693
+ mtimeMs: 123,
694
+ offset: 0,
695
+ content: next.subarray(0, 8),
696
+ });
697
+
698
+ assert.equal(first.done, false);
699
+ const uploadEntries = await readdir(path.join(root, ".offline-sync", "uploads"));
700
+ assert.equal(uploadEntries.includes(staleKey), false);
701
+ assert.equal(uploadEntries.length, 1);
383
702
  } finally {
384
703
  await rm(root, { recursive: true, force: true });
385
704
  }
@@ -458,6 +777,32 @@ test("offline changeset only carries content for changed local files", async ()
458
777
  }
459
778
  });
460
779
 
780
+ test("offline changeset can exclude directly pushed large files without reading their content", async () => {
781
+ const local = await tempDir("remnic-offline-changeset-exclude");
782
+ try {
783
+ await write(local, "state/lcm.sqlite", "before");
784
+ await write(local, "facts/small.md", "before");
785
+ const base = await buildOfflineSyncSnapshot({
786
+ root: local,
787
+ sourceId: "remote",
788
+ includeContent: false,
789
+ });
790
+
791
+ await write(local, "state/lcm.sqlite", "after large");
792
+ await write(local, "facts/small.md", "after small");
793
+ const changeset = await buildOfflineSyncChangeset({
794
+ root: local,
795
+ sourceId: "laptop",
796
+ baseFiles: base.files,
797
+ excludePaths: ["state/lcm.sqlite"],
798
+ });
799
+
800
+ assert.deepEqual(changeset.changes.map((change) => change.path), ["facts/small.md"]);
801
+ } finally {
802
+ await rm(local, { recursive: true, force: true });
803
+ }
804
+ });
805
+
461
806
  test("offline pull accepts metadata-only snapshots when files are unchanged", async () => {
462
807
  const remote = await tempDir("remnic-offline-metadata-remote");
463
808
  const local = await tempDir("remnic-offline-metadata-local");
@@ -892,6 +1237,45 @@ test("offline sync applies and snapshots through secure storage hooks", async ()
892
1237
  "secret fact",
893
1238
  );
894
1239
  assert.equal(snapshot.files[0]?.bytes, Buffer.byteLength("secret fact"));
1240
+
1241
+ const sqlite = Buffer.from("streamed durable sqlite content");
1242
+ const sqliteSha = createHash("sha256").update(sqlite).digest("hex");
1243
+ const first = await applyOfflineSyncFileContentChunk({
1244
+ root,
1245
+ sourceId: "laptop",
1246
+ path: "state/lcm.sqlite",
1247
+ sha256: sqliteSha,
1248
+ bytes: sqlite.byteLength,
1249
+ mtimeMs: 321,
1250
+ offset: 0,
1251
+ content: sqlite.subarray(0, 8),
1252
+ readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
1253
+ writeFile: async ({ filePath, content }) => storage.writeOfflineSyncFile(filePath, content),
1254
+ writeStagingFile: async ({ filePath, content }) => storage.writeOfflineSyncStagingFile(filePath, content),
1255
+ writeFileChunks: async ({ filePath, chunks }) => storage.writeOfflineSyncFileChunks(filePath, chunks),
1256
+ });
1257
+ assert.equal(first.done, false);
1258
+ const second = await applyOfflineSyncFileContentChunk({
1259
+ root,
1260
+ sourceId: "laptop",
1261
+ path: "state/lcm.sqlite",
1262
+ sha256: sqliteSha,
1263
+ bytes: sqlite.byteLength,
1264
+ mtimeMs: 321,
1265
+ offset: 8,
1266
+ content: sqlite.subarray(8),
1267
+ readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
1268
+ writeFile: async ({ filePath, content }) => storage.writeOfflineSyncFile(filePath, content),
1269
+ writeStagingFile: async ({ filePath, content }) => storage.writeOfflineSyncStagingFile(filePath, content),
1270
+ writeFileChunks: async ({ filePath, chunks }) => storage.writeOfflineSyncFileChunks(filePath, chunks),
1271
+ });
1272
+ assert.equal(second.applied, true);
1273
+ const rawSqlite = await readFile(path.join(root, "state", "lcm.sqlite"));
1274
+ assert.equal(isEncryptedFile(rawSqlite), true);
1275
+ assert.equal(
1276
+ (await storage.readOfflineSyncFile(path.join(root, "state", "lcm.sqlite"))).toString("utf8"),
1277
+ "streamed durable sqlite content",
1278
+ );
895
1279
  } finally {
896
1280
  await rm(root, { recursive: true, force: true });
897
1281
  await rm(source, { recursive: true, force: true });
@@ -916,7 +1300,7 @@ test("offline storage writes invalidate fact hash readiness for rebuild", async
916
1300
 
917
1301
  assert.equal(
918
1302
  sourceChangeset.changes.some((change) => change.path.startsWith("state/fact-hashes")),
919
- false,
1303
+ true,
920
1304
  );
921
1305
 
922
1306
  const factChangeset = {