@remnic/core 9.42.0 → 9.43.0

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 (66) hide show
  1. package/dist/access-admin-ops-surface.js +7 -7
  2. package/dist/access-authorization-probe.js +8 -8
  3. package/dist/access-boundary.js +7 -7
  4. package/dist/access-cli.js +19 -19
  5. package/dist/access-extraction-force-flush.js +7 -7
  6. package/dist/access-http.js +12 -12
  7. package/dist/access-identity-continuity-surface.js +7 -7
  8. package/dist/access-lcm-surface.js +7 -7
  9. package/dist/access-mcp.js +10 -10
  10. package/dist/access-namespace-preflight.js +7 -7
  11. package/dist/access-observe-write-surface.js +7 -7
  12. package/dist/access-operations-batch.js +8 -8
  13. package/dist/access-operations.js +9 -9
  14. package/dist/access-recall-concurrency.js +7 -7
  15. package/dist/access-recall-response.js +7 -7
  16. package/dist/access-recall-surface.js +7 -7
  17. package/dist/access-service.js +7 -7
  18. package/dist/{chunk-XRWVXPEM.js → chunk-4DYBLQZC.js} +2 -2
  19. package/dist/{chunk-Q4XL7LJW.js → chunk-5QZEVMLY.js} +4 -4
  20. package/dist/{chunk-KXGN6G2Y.js → chunk-AR2FSZBP.js} +2 -2
  21. package/dist/{chunk-IOQFPVGY.js → chunk-BGXHVOBI.js} +2 -2
  22. package/dist/{chunk-UZQCNOPS.js → chunk-EHKDORQN.js} +4 -4
  23. package/dist/{chunk-7VIB7PAE.js → chunk-EPQ76W5T.js} +2 -2
  24. package/dist/{chunk-PWKZV4A3.js → chunk-GS5UVGQK.js} +2 -2
  25. package/dist/{chunk-FHEJ22B7.js → chunk-JGXXPDM2.js} +2 -2
  26. package/dist/{chunk-UGCUVLOL.js → chunk-KDXD2AM6.js} +11 -11
  27. package/dist/{chunk-UQVGDEAJ.js → chunk-LZ5JKQCS.js} +2 -2
  28. package/dist/{chunk-OYFDEJZB.js → chunk-MBTPUVHZ.js} +2 -2
  29. package/dist/{chunk-LRGSLV7M.js → chunk-PYHOAXTI.js} +4 -4
  30. package/dist/{chunk-CQ5MR2HQ.js → chunk-SEUSFKNS.js} +4 -4
  31. package/dist/{chunk-CWPDM66T.js → chunk-UP67AFRN.js} +5 -5
  32. package/dist/{chunk-AXUU7SG3.js → chunk-YJHU5B3M.js} +2 -2
  33. package/dist/{chunk-6I2C3EOH.js → chunk-ZVVASRUT.js} +7 -7
  34. package/dist/cli.js +18 -18
  35. package/dist/corpus-watermark.js +5 -5
  36. package/dist/index.js +19 -19
  37. package/dist/namespaces/migrate.js +4 -4
  38. package/dist/namespaces/search.js +3 -3
  39. package/dist/operator-doctor-corpus.js +6 -6
  40. package/dist/operator-toolkit.js +8 -8
  41. package/dist/orchestrator.js +19 -19
  42. package/dist/reconcile/cursor.d.ts +21 -0
  43. package/dist/reconcile/cursor.js +104 -0
  44. package/dist/reconcile/cursor.js.map +1 -0
  45. package/dist/search/factory.js +2 -2
  46. package/dist/search/index.js +4 -4
  47. package/dist/transfer/import-sqlite.js +2 -2
  48. package/package.json +12 -2
  49. package/src/reconcile/cursor.test.ts +123 -0
  50. package/src/reconcile/cursor.ts +130 -0
  51. /package/dist/{chunk-XRWVXPEM.js.map → chunk-4DYBLQZC.js.map} +0 -0
  52. /package/dist/{chunk-Q4XL7LJW.js.map → chunk-5QZEVMLY.js.map} +0 -0
  53. /package/dist/{chunk-KXGN6G2Y.js.map → chunk-AR2FSZBP.js.map} +0 -0
  54. /package/dist/{chunk-IOQFPVGY.js.map → chunk-BGXHVOBI.js.map} +0 -0
  55. /package/dist/{chunk-UZQCNOPS.js.map → chunk-EHKDORQN.js.map} +0 -0
  56. /package/dist/{chunk-7VIB7PAE.js.map → chunk-EPQ76W5T.js.map} +0 -0
  57. /package/dist/{chunk-PWKZV4A3.js.map → chunk-GS5UVGQK.js.map} +0 -0
  58. /package/dist/{chunk-FHEJ22B7.js.map → chunk-JGXXPDM2.js.map} +0 -0
  59. /package/dist/{chunk-UGCUVLOL.js.map → chunk-KDXD2AM6.js.map} +0 -0
  60. /package/dist/{chunk-UQVGDEAJ.js.map → chunk-LZ5JKQCS.js.map} +0 -0
  61. /package/dist/{chunk-OYFDEJZB.js.map → chunk-MBTPUVHZ.js.map} +0 -0
  62. /package/dist/{chunk-LRGSLV7M.js.map → chunk-PYHOAXTI.js.map} +0 -0
  63. /package/dist/{chunk-CQ5MR2HQ.js.map → chunk-SEUSFKNS.js.map} +0 -0
  64. /package/dist/{chunk-CWPDM66T.js.map → chunk-UP67AFRN.js.map} +0 -0
  65. /package/dist/{chunk-AXUU7SG3.js.map → chunk-YJHU5B3M.js.map} +0 -0
  66. /package/dist/{chunk-6I2C3EOH.js.map → chunk-ZVVASRUT.js.map} +0 -0
@@ -2,7 +2,10 @@ import {
2
2
  createConversationIndexRuntime,
3
3
  createConversationSearchBackend,
4
4
  createSearchBackend
5
- } from "../chunk-UZQCNOPS.js";
5
+ } from "../chunk-EHKDORQN.js";
6
+ import {
7
+ NoopSearchBackend
8
+ } from "../chunk-CYEPCZN5.js";
6
9
  import {
7
10
  OramaBackend
8
11
  } from "../chunk-ES36HTSC.js";
@@ -22,9 +25,6 @@ import {
22
25
  scanMemoryDir
23
26
  } from "../chunk-W7IGLX4G.js";
24
27
  import "../chunk-CINZGPSJ.js";
25
- import {
26
- NoopSearchBackend
27
- } from "../chunk-CYEPCZN5.js";
28
28
  import "../chunk-LEPOP46W.js";
29
29
  import "../chunk-72A4L5JJ.js";
30
30
  import "../chunk-A7Q43EUZ.js";
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  importSqlite
3
- } from "../chunk-LRGSLV7M.js";
4
- import "../chunk-G4IAEX6D.js";
3
+ } from "../chunk-PYHOAXTI.js";
5
4
  import "../chunk-Z734BLO3.js";
5
+ import "../chunk-G4IAEX6D.js";
6
6
  import "../chunk-RL6NEVXT.js";
7
7
  import "../chunk-GCGJW34D.js";
8
8
  import "../chunk-PZ5AY32C.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/core",
3
- "version": "9.42.0",
3
+ "version": "9.43.0",
4
4
  "description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -166,6 +166,16 @@
166
166
  "remnic-source": "./src/reconcile/plan.ts",
167
167
  "import": "./dist/reconcile/plan.js"
168
168
  },
169
+ "./reconcile/cursor": {
170
+ "types": "./dist/reconcile/cursor.d.ts",
171
+ "remnic-source": "./src/reconcile/cursor.ts",
172
+ "import": "./dist/reconcile/cursor.js"
173
+ },
174
+ "./reconcile/cursor.js": {
175
+ "types": "./dist/reconcile/cursor.d.ts",
176
+ "remnic-source": "./src/reconcile/cursor.ts",
177
+ "import": "./dist/reconcile/cursor.js"
178
+ },
169
179
  "./adapters/codex": {
170
180
  "types": "./dist/adapters/codex.d.ts",
171
181
  "remnic-source": "./src/adapters/codex.ts",
@@ -3086,7 +3096,7 @@
3086
3096
  "core"
3087
3097
  ],
3088
3098
  "peerDependencies": {
3089
- "@remnic/coding-graph": "^9.42.0"
3099
+ "@remnic/coding-graph": "^9.43.0"
3090
3100
  },
3091
3101
  "peerDependenciesMeta": {
3092
3102
  "@remnic/coding-graph": {
@@ -0,0 +1,123 @@
1
+ import * as assert from "node:assert/strict";
2
+ import * as fs from "node:fs/promises";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import { test } from "node:test";
6
+ import {
7
+ defaultConvergeCursorPath,
8
+ hashPeerNamespace,
9
+ normalizeConvergeCursor,
10
+ readConvergeCursor,
11
+ writeConvergeCursor,
12
+ type ConvergeCursorState,
13
+ } from "./cursor.js";
14
+
15
+ const shaA = "a".repeat(64);
16
+
17
+ test("hashPeerNamespace: deterministic key normalization", () => {
18
+ const k1 = hashPeerNamespace("http://peer.example.com/", "default");
19
+ const k2 = hashPeerNamespace("http://peer.example.com", "DEFAULT ");
20
+ assert.equal(k1, k2);
21
+ assert.equal(k1.length, 16);
22
+ });
23
+
24
+ test("hashPeerNamespace: accepts non-URL local peer identifiers", () => {
25
+ assert.equal(hashPeerNamespace("local", "default").length, 16);
26
+ });
27
+
28
+ test("hashPeerNamespace: preserves path case while normalizing URL authority and trailing slash", () => {
29
+ const uppercasePath = hashPeerNamespace("HTTP://PEER.EXAMPLE.COM/Memory/", "default");
30
+ const lowercasePath = hashPeerNamespace("http://peer.example.com/memory", "default");
31
+
32
+ assert.notEqual(uppercasePath, lowercasePath);
33
+ assert.equal(
34
+ uppercasePath,
35
+ hashPeerNamespace("http://peer.example.com/Memory", "DEFAULT "),
36
+ );
37
+ assert.equal(
38
+ lowercasePath,
39
+ hashPeerNamespace("HTTP://PEER.EXAMPLE.COM/memory/", "default"),
40
+ );
41
+ });
42
+
43
+ test("defaultConvergeCursorPath: constructs path under memoryDir state", () => {
44
+ const p = defaultConvergeCursorPath("/tmp/mem", "http://localhost:4318", "general");
45
+ assert.ok(p.includes(path.join(".remnic", "state", "converge-cursors")));
46
+ assert.ok(p.endsWith(".json"));
47
+ });
48
+
49
+ test("normalizeConvergeCursor: validates envelope shape", () => {
50
+ assert.throws(() => normalizeConvergeCursor(null), /must be an object/);
51
+ assert.throws(() => normalizeConvergeCursor({ version: 2 }), /version must be 1/);
52
+ assert.throws(
53
+ () => normalizeConvergeCursor({ version: 1, peerUrl: "" }),
54
+ /missing peerUrl/,
55
+ );
56
+ assert.throws(
57
+ () => normalizeConvergeCursor({ version: 1, peerUrl: "http://peer", namespace: "" }),
58
+ /missing namespace/,
59
+ );
60
+
61
+ const valid = normalizeConvergeCursor({
62
+ version: 1,
63
+ peerUrl: "http://peer",
64
+ namespace: "default",
65
+ baseFiles: [{ path: "a.md", sha256: shaA }],
66
+ });
67
+ assert.equal(valid.peerUrl, "http://peer");
68
+ assert.equal(valid.namespace, "default");
69
+ assert.equal(valid.baseFiles.length, 1);
70
+ assert.equal(valid.baseFiles[0]?.path, "a.md");
71
+ });
72
+
73
+ test("readConvergeCursor: returns null when file is missing or invalid", async () => {
74
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "remnic-cursor-test-"));
75
+ try {
76
+ const missing = await readConvergeCursor(path.join(tmpDir, "missing.json"));
77
+ assert.equal(missing, null);
78
+
79
+ const invalidPath = path.join(tmpDir, "invalid.json");
80
+ await fs.writeFile(invalidPath, "{ invalid json", "utf-8");
81
+ const invalid = await readConvergeCursor(invalidPath);
82
+ assert.equal(invalid, null);
83
+ } finally {
84
+ await fs.rm(tmpDir, { recursive: true, force: true });
85
+ }
86
+ });
87
+
88
+ test("writeConvergeCursor & readConvergeCursor: atomic write roundtrip", async () => {
89
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "remnic-cursor-test-"));
90
+ const cursorPath = path.join(tmpDir, "state", "cursor.json");
91
+ try {
92
+ const cursor: ConvergeCursorState = {
93
+ version: 1,
94
+ peerUrl: "http://localhost:4318",
95
+ namespace: "default",
96
+ lastConvergedAt: new Date().toISOString(),
97
+ baseFiles: [{ path: "facts/a.md", sha256: shaA, bytes: 120, mtimeMs: 1000 }],
98
+ completedPaths: ["facts/a.md"],
99
+ };
100
+
101
+ await writeConvergeCursor(cursorPath, cursor);
102
+ const read = await readConvergeCursor(cursorPath);
103
+ assert.ok(read);
104
+ assert.equal(read.peerUrl, "http://localhost:4318");
105
+ assert.equal(read.namespace, "default");
106
+ assert.equal(read.baseFiles.length, 1);
107
+ assert.equal(read.baseFiles[0]?.sha256, shaA);
108
+ assert.deepEqual(read.completedPaths, ["facts/a.md"]);
109
+
110
+ // Update existing cursor atomically
111
+ const updated: ConvergeCursorState = {
112
+ ...read,
113
+ lastConvergedAt: new Date().toISOString(),
114
+ completedPaths: ["facts/a.md", "facts/b.md"],
115
+ };
116
+ await writeConvergeCursor(cursorPath, updated);
117
+ const readUpdated = await readConvergeCursor(cursorPath);
118
+ assert.ok(readUpdated);
119
+ assert.equal(readUpdated.completedPaths?.length, 2);
120
+ } finally {
121
+ await fs.rm(tmpDir, { recursive: true, force: true });
122
+ }
123
+ });
@@ -0,0 +1,130 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import * as fs from "node:fs/promises";
3
+ import * as path from "node:path";
4
+
5
+ export interface ConvergeCursorFileState {
6
+ path: string;
7
+ sha256: string;
8
+ mtimeMs?: number;
9
+ bytes?: number;
10
+ }
11
+
12
+ export interface ConvergeCursorState {
13
+ version: 1;
14
+ peerUrl: string;
15
+ namespace: string;
16
+ lastConvergedAt?: string;
17
+ baseFiles: ConvergeCursorFileState[];
18
+ completedPaths?: string[];
19
+ }
20
+
21
+ export function hashPeerNamespace(peerUrl: string, namespace: string): string {
22
+ let normalizedUrl: string;
23
+ try {
24
+ const url = new URL(peerUrl);
25
+ const credentials =
26
+ url.username || url.password
27
+ ? `${url.username}${url.password ? `:${url.password}` : ""}@`
28
+ : "";
29
+ normalizedUrl =
30
+ `${url.protocol.toLowerCase()}//${credentials}${url.hostname.toLowerCase()}` +
31
+ `${url.port ? `:${url.port}` : ""}${url.pathname.replace(/\/+$/, "")}${url.search}${url.hash}`;
32
+ } catch {
33
+ normalizedUrl = peerUrl.trim().replace(/\/+$/, "").toLowerCase();
34
+ }
35
+ const normalizedNs = namespace.trim().toLowerCase();
36
+ return createHash("sha256")
37
+ .update(`${normalizedUrl}\0${normalizedNs}`)
38
+ .digest("hex")
39
+ .slice(0, 16);
40
+ }
41
+
42
+ export function defaultConvergeCursorPath(
43
+ memoryDir: string,
44
+ peerUrl: string,
45
+ namespace: string,
46
+ ): string {
47
+ const key = hashPeerNamespace(peerUrl, namespace);
48
+ return path.join(path.resolve(memoryDir), ".remnic", "state", "converge-cursors", `${key}.json`);
49
+ }
50
+
51
+ export function normalizeConvergeCursor(input: unknown): ConvergeCursorState {
52
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
53
+ throw new Error("converge cursor must be an object");
54
+ }
55
+ const obj = input as Record<string, unknown>;
56
+ if (obj.version !== 1) {
57
+ throw new Error("converge cursor version must be 1");
58
+ }
59
+ if (typeof obj.peerUrl !== "string" || !obj.peerUrl.trim()) {
60
+ throw new Error("converge cursor missing peerUrl");
61
+ }
62
+ if (typeof obj.namespace !== "string" || !obj.namespace.trim()) {
63
+ throw new Error("converge cursor missing namespace");
64
+ }
65
+ const baseFiles: ConvergeCursorFileState[] = [];
66
+ if (Array.isArray(obj.baseFiles)) {
67
+ for (const item of obj.baseFiles) {
68
+ if (item && typeof item === "object") {
69
+ const fileItem = item as Record<string, unknown>;
70
+ if (typeof fileItem.path === "string" && typeof fileItem.sha256 === "string") {
71
+ baseFiles.push({
72
+ path: fileItem.path,
73
+ sha256: fileItem.sha256,
74
+ mtimeMs: typeof fileItem.mtimeMs === "number" ? fileItem.mtimeMs : undefined,
75
+ bytes: typeof fileItem.bytes === "number" ? fileItem.bytes : undefined,
76
+ });
77
+ }
78
+ }
79
+ }
80
+ }
81
+ const completedPaths: string[] = [];
82
+ if (Array.isArray(obj.completedPaths)) {
83
+ for (const item of obj.completedPaths) {
84
+ if (typeof item === "string") {
85
+ completedPaths.push(item);
86
+ }
87
+ }
88
+ }
89
+ return {
90
+ version: 1,
91
+ peerUrl: obj.peerUrl.trim(),
92
+ namespace: obj.namespace.trim(),
93
+ lastConvergedAt: typeof obj.lastConvergedAt === "string" ? obj.lastConvergedAt : undefined,
94
+ baseFiles,
95
+ completedPaths,
96
+ };
97
+ }
98
+
99
+ export async function readConvergeCursor(
100
+ cursorPath: string,
101
+ ): Promise<ConvergeCursorState | null> {
102
+ try {
103
+ const raw = await fs.readFile(path.resolve(cursorPath), "utf-8");
104
+ const parsed = JSON.parse(raw);
105
+ return normalizeConvergeCursor(parsed);
106
+ } catch (error) {
107
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
108
+ return null;
109
+ }
110
+ }
111
+
112
+ export async function writeConvergeCursor(
113
+ cursorPath: string,
114
+ cursor: ConvergeCursorState,
115
+ ): Promise<void> {
116
+ const normalized = normalizeConvergeCursor(cursor);
117
+ const target = path.resolve(cursorPath);
118
+ await fs.mkdir(path.dirname(target), { recursive: true });
119
+ const tmp = path.join(
120
+ path.dirname(target),
121
+ `.converge-cursor.${process.pid}.${randomUUID()}.tmp`,
122
+ );
123
+ await fs.writeFile(tmp, JSON.stringify(normalized, null, 2) + "\n", "utf-8");
124
+ try {
125
+ await fs.rename(tmp, target);
126
+ } catch (error) {
127
+ await fs.unlink(tmp).catch(() => {});
128
+ throw error;
129
+ }
130
+ }