@mneme-ai/core 2.19.96 → 2.19.97

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.
@@ -0,0 +1,218 @@
1
+ /**
2
+ * v2.19.97 — SUPERLOCK + DEV-SOURCE GUARD.
3
+ *
4
+ * Solves the race condition class user reported in the post-mortem of
5
+ * their broken install:
6
+ *
7
+ * 1. Pulse banner advertises [AUTO-ACTION] mneme.system.upgrade
8
+ * 2. Daemon (or shepherd) starts auto-upgrade in background
9
+ * → deletes bot/platforms/index.js to rewrite
10
+ * 3. User simultaneously runs `npm install -g mneme-ai` from terminal
11
+ * 4. EBUSY on libvips DLL because daemon is mid-write
12
+ * 5. Half-finished install: missing files; every `mneme` command
13
+ * throws ERR_MODULE_NOT_FOUND
14
+ *
15
+ * Plus the user discovered a SECOND install source: when Mneme is
16
+ * checked out as a dev repo (D:\lib_ai_git\packages\cli\bin\mneme.js),
17
+ * THAT daemon ALSO honours pulse auto-upgrade mandates — and tries to
18
+ * upgrade the *global npm install*, racing with whatever the user is
19
+ * doing. Two install roots → two daemons → two upgrade triggers →
20
+ * guaranteed race.
21
+ *
22
+ * Two-layer fix:
23
+ *
24
+ * DEV-SOURCE GUARD
25
+ * Detect when the running mneme binary is from a source checkout
26
+ * (path contains `packages/cli/bin/`) rather than a published
27
+ * `node_modules/mneme-ai/` tree. When detected, refuse ALL
28
+ * auto-upgrade attempts + drop the upgrade [AUTO-ACTION] from
29
+ * pulse output entirely. Dev source manages its own upgrades
30
+ * via git pull.
31
+ *
32
+ * SUPERLOCK
33
+ * Single global file mutex at `~/.mneme-global/superlock.flag`
34
+ * that EVERY install/upgrade path must grab before mutating the
35
+ * on-disk install. Lock content is JSON with { pid, startedAt,
36
+ * intent, role } so observers can see who's holding it + why.
37
+ * Stale locks (>10 min) are auto-broken. Lock acquisition is
38
+ * atomic-rename (POSIX + Windows safe).
39
+ *
40
+ * Composes with v2.19.58 install-incoming.flag (5-min flag window),
41
+ * v2.19.57 shepherd, v2.19.62 phoenix DLL extraction, but is the
42
+ * single source of truth that serialises EVERYTHING.
43
+ */
44
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync, renameSync } from "node:fs";
45
+ import { join, normalize } from "node:path";
46
+ import { homedir } from "node:os";
47
+ import { randomBytes } from "node:crypto";
48
+ const GLOBAL_DIR = ".mneme-global";
49
+ const LOCK = "superlock.flag";
50
+ const STALE_MS = 10 * 60 * 1000; // 10 min — anything older is presumed dead
51
+ function lockDir() {
52
+ const d = join(homedir(), GLOBAL_DIR);
53
+ if (!existsSync(d)) {
54
+ try {
55
+ mkdirSync(d, { recursive: true });
56
+ }
57
+ catch { /* */ }
58
+ }
59
+ return d;
60
+ }
61
+ function lockPath() { return join(lockDir(), LOCK); }
62
+ /** Read current lock state if any. Returns null if no lock or unreadable. */
63
+ export function readLock() {
64
+ const p = lockPath();
65
+ if (!existsSync(p))
66
+ return null;
67
+ try {
68
+ return JSON.parse(readFileSync(p, "utf8"));
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ }
74
+ /** True iff a lock exists AND is fresh enough to honour. Stale locks
75
+ * (older than STALE_MS) are ignored because the holder probably died. */
76
+ export function isLockHeld() {
77
+ const s = readLock();
78
+ if (!s)
79
+ return false;
80
+ try {
81
+ const age = Date.now() - new Date(s.startedAt).getTime();
82
+ return age < STALE_MS;
83
+ }
84
+ catch {
85
+ return false;
86
+ }
87
+ }
88
+ /** Atomic acquire. Returns true if we now hold the lock; false if
89
+ * somebody else holds a fresh one. Uses tmpfile + rename for atomicity
90
+ * on both POSIX and Windows. */
91
+ export function acquireLock(state) {
92
+ // Check existing lock.
93
+ const existing = readLock();
94
+ if (existing) {
95
+ const age = Date.now() - new Date(existing.startedAt).getTime();
96
+ if (age < STALE_MS && existing.pid !== state.pid)
97
+ return false;
98
+ // Stale — break it.
99
+ try {
100
+ unlinkSync(lockPath());
101
+ }
102
+ catch { /* */ }
103
+ }
104
+ // Atomic: write to tmp then rename. Two concurrent acquirers race on
105
+ // rename; one wins, the other's rename overwrites — both think they
106
+ // won. Belt-and-suspenders: re-read after writing and check pid.
107
+ const tmp = join(lockDir(), `superlock.${process.pid}.${randomBytes(4).toString("hex")}.tmp`);
108
+ const full = { ...state, startedAt: new Date().toISOString() };
109
+ try {
110
+ writeFileSync(tmp, JSON.stringify(full, null, 2), "utf8");
111
+ renameSync(tmp, lockPath());
112
+ }
113
+ catch {
114
+ try {
115
+ unlinkSync(tmp);
116
+ }
117
+ catch { /* */ }
118
+ return false;
119
+ }
120
+ // Verify we're the winner.
121
+ const s = readLock();
122
+ return !!s && s.pid === state.pid;
123
+ }
124
+ /** Release the lock if we still hold it. Caller MUST always call this
125
+ * in a `finally` block to avoid leaks. */
126
+ export function releaseLock(pid = process.pid) {
127
+ const s = readLock();
128
+ if (!s)
129
+ return true;
130
+ if (s.pid !== pid)
131
+ return false; // somebody else owns it now
132
+ try {
133
+ unlinkSync(lockPath());
134
+ return true;
135
+ }
136
+ catch {
137
+ return false;
138
+ }
139
+ }
140
+ /** Wrap any async install/upgrade action with the superlock. Throws
141
+ * with a descriptive error if the lock is held by another fresh
142
+ * process. */
143
+ export async function withSuperlock(state, fn) {
144
+ const acquired = acquireLock({ ...state, pid: process.pid });
145
+ if (!acquired) {
146
+ const held = readLock();
147
+ throw new Error(`SUPERLOCK: another mneme install/upgrade is in progress` +
148
+ (held ? ` (pid=${held.pid}, role=${held.role}, intent=${held.intent}, since ${held.startedAt})` : "") +
149
+ `. Wait for it to finish, or run \`mneme superlock --break\` if you believe it is stuck.`);
150
+ }
151
+ try {
152
+ return await fn();
153
+ }
154
+ finally {
155
+ releaseLock();
156
+ }
157
+ }
158
+ // ─── DEV-SOURCE GUARD ──────────────────────────────────────────────────
159
+ /** True iff the currently-running mneme binary lives in a source
160
+ * checkout (path contains `/packages/cli/` or `\packages\cli\`) rather
161
+ * than a node_modules install. Dev-source must NEVER auto-upgrade
162
+ * itself — git pull is the right tool for that. */
163
+ export function isDevSource(scriptPath) {
164
+ // Distinguish "caller passed nothing" (use argv default) from "caller
165
+ // explicitly passed empty" (refuse to guess) — the latter should
166
+ // return false so callers can opt out of process-state inspection.
167
+ const raw = scriptPath === undefined ? (process.argv[1] ?? "") : scriptPath;
168
+ if (!raw)
169
+ return false;
170
+ const path = normalize(raw);
171
+ // Patterns that indicate dev checkout:
172
+ // .../packages/cli/bin/mneme.js
173
+ // .../packages/cli/dist/index.js
174
+ // .../packages/cli/src/index.ts
175
+ const devPatterns = [
176
+ /[\\/]packages[\\/]cli[\\/]/,
177
+ /[\\/]lib_ai_git[\\/]/,
178
+ /[\\/]mneme-ai[\\/]packages[\\/]/,
179
+ ];
180
+ // Conversely, definitely-not-dev-source patterns (real install):
181
+ const installPatterns = [
182
+ /[\\/]node_modules[\\/]mneme-ai[\\/]/,
183
+ /[\\/]\.npm-global[\\/]/,
184
+ /[\\/]npm[\\/]node_modules[\\/]mneme-ai[\\/]/,
185
+ ];
186
+ for (const p of installPatterns)
187
+ if (p.test(path))
188
+ return false;
189
+ for (const p of devPatterns)
190
+ if (p.test(path))
191
+ return true;
192
+ return false;
193
+ }
194
+ /** Human-readable explanation surfaced when dev-source guard fires. */
195
+ export function devSourceMessage(scriptPath) {
196
+ const path = scriptPath ?? process.argv[1] ?? "(unknown)";
197
+ return [
198
+ `Mneme is running from a dev source checkout, not a published npm install.`,
199
+ ` binary path: ${path}`,
200
+ ` auto-upgrade: REFUSED (dev source manages itself via git pull)`,
201
+ `To pull latest dev changes: cd <repo> && git pull && pnpm install`,
202
+ `To use the published version: install via npm install -g mneme-ai in a separate shell.`,
203
+ ].join("\n");
204
+ }
205
+ /** The decision the pulse renderer + auto-upgrade hooks call before
206
+ * emitting `[AUTO-ACTION] mneme.system.upgrade`. Returns true when
207
+ * the auto-action is safe to advertise + execute. */
208
+ export function autoUpgradeAllowed(scriptPath) {
209
+ if (isDevSource(scriptPath)) {
210
+ return { allowed: false, reason: "dev-source-detected" };
211
+ }
212
+ if (isLockHeld()) {
213
+ const s = readLock();
214
+ return { allowed: false, reason: `superlock-held-by-${s?.role ?? "unknown"}` };
215
+ }
216
+ return { allowed: true, reason: "ok" };
217
+ }
218
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/superlock/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=superlock.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"superlock.test.d.ts","sourceRoot":"","sources":["../../src/superlock/superlock.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,105 @@
1
+ import { describe, expect, it, beforeEach, afterEach } from "vitest";
2
+ import { isDevSource, autoUpgradeAllowed, acquireLock, releaseLock, isLockHeld, readLock, withSuperlock, devSourceMessage } from "./index.js";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { existsSync, unlinkSync } from "node:fs";
6
+ const LOCK_PATH = join(homedir(), ".mneme-global", "superlock.flag");
7
+ describe("superlock", () => {
8
+ describe("isDevSource", () => {
9
+ it("recognises a dev checkout path", () => {
10
+ expect(isDevSource("D:\\lib_ai_git\\packages\\cli\\bin\\mneme.js")).toBe(true);
11
+ expect(isDevSource("/Users/dev/lib_ai_git/packages/cli/bin/mneme.js")).toBe(true);
12
+ expect(isDevSource("C:\\src\\mneme-ai\\packages\\cli\\dist\\index.js")).toBe(true);
13
+ });
14
+ it("recognises a node_modules install as NOT dev", () => {
15
+ expect(isDevSource("C:\\nvm4w\\nodejs\\node_modules\\mneme-ai\\bin\\mneme.js")).toBe(false);
16
+ expect(isDevSource("/usr/local/lib/node_modules/mneme-ai/bin/mneme.js")).toBe(false);
17
+ expect(isDevSource("/Users/x/.npm-global/lib/node_modules/mneme-ai/bin/mneme.js")).toBe(false);
18
+ });
19
+ it("returns false on explicit empty path (no argv fallback when caller opts out)", () => {
20
+ expect(isDevSource("")).toBe(false);
21
+ });
22
+ });
23
+ describe("autoUpgradeAllowed", () => {
24
+ beforeEach(() => { try {
25
+ if (existsSync(LOCK_PATH))
26
+ unlinkSync(LOCK_PATH);
27
+ }
28
+ catch { /* */ } });
29
+ afterEach(() => { try {
30
+ if (existsSync(LOCK_PATH))
31
+ unlinkSync(LOCK_PATH);
32
+ }
33
+ catch { /* */ } });
34
+ it("refuses upgrade when running from dev source", () => {
35
+ const r = autoUpgradeAllowed("D:\\lib_ai_git\\packages\\cli\\bin\\mneme.js");
36
+ expect(r.allowed).toBe(false);
37
+ expect(r.reason).toBe("dev-source-detected");
38
+ });
39
+ it("allows upgrade from real npm install when no lock held", () => {
40
+ const r = autoUpgradeAllowed("/usr/local/lib/node_modules/mneme-ai/bin/mneme.js");
41
+ expect(r.allowed).toBe(true);
42
+ expect(r.reason).toBe("ok");
43
+ });
44
+ });
45
+ describe("acquire/release lock", () => {
46
+ beforeEach(() => { try {
47
+ if (existsSync(LOCK_PATH))
48
+ unlinkSync(LOCK_PATH);
49
+ }
50
+ catch { /* */ } });
51
+ afterEach(() => { try {
52
+ if (existsSync(LOCK_PATH))
53
+ unlinkSync(LOCK_PATH);
54
+ }
55
+ catch { /* */ } });
56
+ it("first acquire succeeds, second from different pid fails", () => {
57
+ expect(acquireLock({ pid: 11111, role: "test", intent: "first try", holderPath: "/tmp/a" })).toBe(true);
58
+ expect(isLockHeld()).toBe(true);
59
+ // Try to acquire as a different pid while fresh lock is held.
60
+ expect(acquireLock({ pid: 22222, role: "test", intent: "second try", holderPath: "/tmp/b" })).toBe(false);
61
+ });
62
+ it("release lets the next acquirer in", () => {
63
+ expect(acquireLock({ pid: process.pid, role: "test", intent: "self", holderPath: "/tmp/x" })).toBe(true);
64
+ expect(releaseLock(process.pid)).toBe(true);
65
+ expect(isLockHeld()).toBe(false);
66
+ expect(acquireLock({ pid: 33333, role: "test", intent: "next", holderPath: "/tmp/y" })).toBe(true);
67
+ });
68
+ it("readLock returns null when no lock exists", () => {
69
+ expect(readLock()).toBeNull();
70
+ });
71
+ });
72
+ describe("withSuperlock", () => {
73
+ beforeEach(() => { try {
74
+ if (existsSync(LOCK_PATH))
75
+ unlinkSync(LOCK_PATH);
76
+ }
77
+ catch { /* */ } });
78
+ afterEach(() => { try {
79
+ if (existsSync(LOCK_PATH))
80
+ unlinkSync(LOCK_PATH);
81
+ }
82
+ catch { /* */ } });
83
+ it("runs fn, releases lock on success", async () => {
84
+ const result = await withSuperlock({ role: "test", intent: "demo", holderPath: "/tmp/z" }, async () => {
85
+ expect(isLockHeld()).toBe(true);
86
+ return 42;
87
+ });
88
+ expect(result).toBe(42);
89
+ expect(isLockHeld()).toBe(false);
90
+ });
91
+ it("releases lock even when fn throws", async () => {
92
+ await expect(withSuperlock({ role: "test", intent: "boom", holderPath: "/tmp/z" }, async () => { throw new Error("boom"); })).rejects.toThrow("boom");
93
+ expect(isLockHeld()).toBe(false);
94
+ });
95
+ });
96
+ describe("devSourceMessage", () => {
97
+ it("includes path, refusal, and remediation", () => {
98
+ const msg = devSourceMessage("D:\\lib_ai_git\\packages\\cli\\bin\\mneme.js");
99
+ expect(msg).toContain("dev source");
100
+ expect(msg).toContain("REFUSED");
101
+ expect(msg).toContain("git pull");
102
+ });
103
+ });
104
+ });
105
+ //# sourceMappingURL=superlock.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"superlock.test.js","sourceRoot":"","sources":["../../src/superlock/superlock.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mneme-ai/core",
3
- "version": "2.19.96",
3
+ "version": "2.19.97",
4
4
  "description": "Core indexing, retrieval, and graph engine for Mneme",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",