@mneme-ai/core 2.67.0 → 2.68.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.
- package/dist/protoplasm/criu_pickle.d.ts +49 -0
- package/dist/protoplasm/criu_pickle.d.ts.map +1 -0
- package/dist/protoplasm/criu_pickle.js +73 -0
- package/dist/protoplasm/criu_pickle.js.map +1 -0
- package/dist/protoplasm/extensions.test.d.ts +8 -0
- package/dist/protoplasm/extensions.test.d.ts.map +1 -0
- package/dist/protoplasm/extensions.test.js +175 -0
- package/dist/protoplasm/extensions.test.js.map +1 -0
- package/dist/protoplasm/hydra_quorum.d.ts +43 -0
- package/dist/protoplasm/hydra_quorum.d.ts.map +1 -0
- package/dist/protoplasm/hydra_quorum.js +124 -0
- package/dist/protoplasm/hydra_quorum.js.map +1 -0
- package/dist/protoplasm/index.d.ts +10 -0
- package/dist/protoplasm/index.d.ts.map +1 -1
- package/dist/protoplasm/index.js +11 -0
- package/dist/protoplasm/index.js.map +1 -1
- package/dist/protoplasm/lan_gossip.d.ts +75 -0
- package/dist/protoplasm/lan_gossip.d.ts.map +1 -0
- package/dist/protoplasm/lan_gossip.js +140 -0
- package/dist/protoplasm/lan_gossip.js.map +1 -0
- package/dist/protoplasm/ts_auto_wrap.d.ts +53 -0
- package/dist/protoplasm/ts_auto_wrap.d.ts.map +1 -0
- package/dist/protoplasm/ts_auto_wrap.js +102 -0
- package/dist/protoplasm/ts_auto_wrap.js.map +1 -0
- package/dist/protoplasm/usb_soul.d.ts +35 -0
- package/dist/protoplasm/usb_soul.d.ts.map +1 -0
- package/dist/protoplasm/usb_soul.js +162 -0
- package/dist/protoplasm/usb_soul.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🧙 PROTOPLASM — CRIU PROCESS PICKLE (Linux-only)
|
|
3
|
+
*
|
|
4
|
+
* Use Linux CRIU (Checkpoint/Restore In Userspace) to snapshot a live
|
|
5
|
+
* process to disk + later restore including in-memory state + open FDs.
|
|
6
|
+
*
|
|
7
|
+
* Requirements:
|
|
8
|
+
* - Linux kernel ≥ 3.11 with CONFIG_CHECKPOINT_RESTORE=y
|
|
9
|
+
* - criu binary in PATH
|
|
10
|
+
* - CAP_SYS_ADMIN (typically requires root or container with privileges)
|
|
11
|
+
*
|
|
12
|
+
* On non-Linux (Windows/macOS): functions return { ok: false, reason: "unsupported_platform" }
|
|
13
|
+
* No-op + warning instead of throwing.
|
|
14
|
+
*
|
|
15
|
+
* This module is INTENTIONALLY conservative — CRIU on its own can corrupt
|
|
16
|
+
* process state if invoked carelessly (FD handles, network sockets, mmap'd
|
|
17
|
+
* files). The wrapper:
|
|
18
|
+
* - probes criu availability before any call
|
|
19
|
+
* - dumps to per-PID timestamped dir
|
|
20
|
+
* - never auto-restores; restore is explicit user action
|
|
21
|
+
*/
|
|
22
|
+
export interface CriuAvailability {
|
|
23
|
+
supported: boolean;
|
|
24
|
+
platform: NodeJS.Platform;
|
|
25
|
+
criuPath?: string;
|
|
26
|
+
version?: string;
|
|
27
|
+
reason?: string;
|
|
28
|
+
needsRoot?: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface CriuSnapshotResult {
|
|
31
|
+
ok: boolean;
|
|
32
|
+
imageDir?: string;
|
|
33
|
+
pid?: number;
|
|
34
|
+
reason?: string;
|
|
35
|
+
stderr?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface CriuRestoreResult {
|
|
38
|
+
ok: boolean;
|
|
39
|
+
newPid?: number;
|
|
40
|
+
reason?: string;
|
|
41
|
+
stderr?: string;
|
|
42
|
+
}
|
|
43
|
+
export declare function probeCriu(): CriuAvailability;
|
|
44
|
+
/** Dump live process to image directory. Caller is responsible for picking
|
|
45
|
+
* pid that is safe to dump (no in-flight sockets to external services etc). */
|
|
46
|
+
export declare function snapshot(pid: number, ledgerDir: string): CriuSnapshotResult;
|
|
47
|
+
/** Restore a previously-snapshot'd process from imageDir. */
|
|
48
|
+
export declare function restore(imageDir: string): CriuRestoreResult;
|
|
49
|
+
//# sourceMappingURL=criu_pickle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"criu_pickle.d.ts","sourceRoot":"","sources":["../../src/protoplasm/criu_pickle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAMH,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,SAAS,IAAI,gBAAgB,CAe5C;AAED;gFACgF;AAChF,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,kBAAkB,CAS3E;AAED,6DAA6D;AAC7D,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,iBAAiB,CAQ3D"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🧙 PROTOPLASM — CRIU PROCESS PICKLE (Linux-only)
|
|
3
|
+
*
|
|
4
|
+
* Use Linux CRIU (Checkpoint/Restore In Userspace) to snapshot a live
|
|
5
|
+
* process to disk + later restore including in-memory state + open FDs.
|
|
6
|
+
*
|
|
7
|
+
* Requirements:
|
|
8
|
+
* - Linux kernel ≥ 3.11 with CONFIG_CHECKPOINT_RESTORE=y
|
|
9
|
+
* - criu binary in PATH
|
|
10
|
+
* - CAP_SYS_ADMIN (typically requires root or container with privileges)
|
|
11
|
+
*
|
|
12
|
+
* On non-Linux (Windows/macOS): functions return { ok: false, reason: "unsupported_platform" }
|
|
13
|
+
* No-op + warning instead of throwing.
|
|
14
|
+
*
|
|
15
|
+
* This module is INTENTIONALLY conservative — CRIU on its own can corrupt
|
|
16
|
+
* process state if invoked carelessly (FD handles, network sockets, mmap'd
|
|
17
|
+
* files). The wrapper:
|
|
18
|
+
* - probes criu availability before any call
|
|
19
|
+
* - dumps to per-PID timestamped dir
|
|
20
|
+
* - never auto-restores; restore is explicit user action
|
|
21
|
+
*/
|
|
22
|
+
import { execSync, spawnSync } from "node:child_process";
|
|
23
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
24
|
+
import { join } from "node:path";
|
|
25
|
+
export function probeCriu() {
|
|
26
|
+
const plat = process.platform;
|
|
27
|
+
if (plat !== "linux") {
|
|
28
|
+
return { supported: false, platform: plat, reason: "CRIU is Linux-only — Windows/macOS not supported" };
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const which = execSync("which criu", { encoding: "utf8" }).trim();
|
|
32
|
+
if (!which)
|
|
33
|
+
return { supported: false, platform: plat, reason: "criu binary not in PATH" };
|
|
34
|
+
let version = "";
|
|
35
|
+
try {
|
|
36
|
+
version = execSync("criu --version", { encoding: "utf8" }).split("\n")[0];
|
|
37
|
+
}
|
|
38
|
+
catch { /* */ }
|
|
39
|
+
const isRoot = (typeof process.getuid === "function") && process.getuid() === 0;
|
|
40
|
+
return { supported: true, platform: plat, criuPath: which, version, needsRoot: !isRoot };
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
return { supported: false, platform: plat, reason: e.message };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Dump live process to image directory. Caller is responsible for picking
|
|
47
|
+
* pid that is safe to dump (no in-flight sockets to external services etc). */
|
|
48
|
+
export function snapshot(pid, ledgerDir) {
|
|
49
|
+
const avail = probeCriu();
|
|
50
|
+
if (!avail.supported)
|
|
51
|
+
return { ok: false, reason: avail.reason ?? "unsupported" };
|
|
52
|
+
const imageDir = join(ledgerDir, "criu", `pid-${pid}-${Date.now()}`);
|
|
53
|
+
mkdirSync(imageDir, { recursive: true });
|
|
54
|
+
const args = ["dump", "--tree", String(pid), "--images-dir", imageDir, "--shell-job"];
|
|
55
|
+
const r = spawnSync("criu", args, { encoding: "utf8" });
|
|
56
|
+
if (r.status !== 0)
|
|
57
|
+
return { ok: false, imageDir, pid, reason: `criu dump exit=${r.status}`, stderr: r.stderr ?? "" };
|
|
58
|
+
return { ok: true, imageDir, pid };
|
|
59
|
+
}
|
|
60
|
+
/** Restore a previously-snapshot'd process from imageDir. */
|
|
61
|
+
export function restore(imageDir) {
|
|
62
|
+
const avail = probeCriu();
|
|
63
|
+
if (!avail.supported)
|
|
64
|
+
return { ok: false, reason: avail.reason ?? "unsupported" };
|
|
65
|
+
if (!existsSync(imageDir))
|
|
66
|
+
return { ok: false, reason: `image dir not found: ${imageDir}` };
|
|
67
|
+
const r = spawnSync("criu", ["restore", "--images-dir", imageDir, "--shell-job"], { encoding: "utf8" });
|
|
68
|
+
if (r.status !== 0)
|
|
69
|
+
return { ok: false, reason: `criu restore exit=${r.status}`, stderr: r.stderr ?? "" };
|
|
70
|
+
// criu restore replaces current process; newPid not available from parent perspective
|
|
71
|
+
return { ok: true };
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=criu_pickle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"criu_pickle.js","sourceRoot":"","sources":["../../src/protoplasm/criu_pickle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extensions.test.d.ts","sourceRoot":"","sources":["../../src/protoplasm/extensions.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🧬 PROTOPLASM — v2.68.0 extension tests
|
|
3
|
+
*
|
|
4
|
+
* Pin invariants for USB SOUL / HYDRA QUORUM / LAN GOSSIP / TS AUTO-WRAP /
|
|
5
|
+
* CRIU PICKLE. Tests skip the OS-specific bits where they cannot run.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
8
|
+
import { mkdtempSync, rmSync, writeFileSync, readFileSync, existsSync } from "node:fs";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { syncTo, syncFrom, verifyMount } from "./usb_soul.js";
|
|
12
|
+
import { statusHydra, tryBecomePrimary, refreshPrimary, releasePrimary } from "./hydra_quorum.js";
|
|
13
|
+
import { LanGossip, GOSSIP_TUNING } from "./lan_gossip.js";
|
|
14
|
+
import { scanSourceFile, rewriteSourceFile } from "./ts_auto_wrap.js";
|
|
15
|
+
import { probeCriu, snapshot, restore } from "./criu_pickle.js";
|
|
16
|
+
let tmpDir;
|
|
17
|
+
beforeEach(() => { tmpDir = mkdtempSync(join(tmpdir(), "protoplasm-ext-")); });
|
|
18
|
+
afterEach(() => { try {
|
|
19
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
20
|
+
}
|
|
21
|
+
catch { /* */ } });
|
|
22
|
+
describe("USB SOUL", () => {
|
|
23
|
+
it("syncTo + syncFrom round-trip preserves files", () => {
|
|
24
|
+
const ledger = join(tmpDir, "ledger");
|
|
25
|
+
const mount = join(tmpDir, "fake-usb");
|
|
26
|
+
require("node:fs").mkdirSync(ledger, { recursive: true });
|
|
27
|
+
require("node:fs").mkdirSync(mount, { recursive: true });
|
|
28
|
+
writeFileSync(join(ledger, "wal.jsonl"), "wal-content");
|
|
29
|
+
writeFileSync(join(ledger, "findings.jsonl"), "findings-content");
|
|
30
|
+
writeFileSync(join(ledger, ".key"), "secret-key-32-chars-min-please-ok");
|
|
31
|
+
const out = syncTo(mount, ledger);
|
|
32
|
+
expect(out.ok).toBe(true);
|
|
33
|
+
expect(out.copied?.length).toBe(3);
|
|
34
|
+
const restoreDir = join(tmpDir, "restored");
|
|
35
|
+
const r = syncFrom(mount, restoreDir);
|
|
36
|
+
expect(r.ok).toBe(true);
|
|
37
|
+
expect(readFileSync(join(restoreDir, "wal.jsonl"), "utf8")).toBe("wal-content");
|
|
38
|
+
expect(readFileSync(join(restoreDir, ".key"), "utf8")).toBe("secret-key-32-chars-min-please-ok");
|
|
39
|
+
});
|
|
40
|
+
it("verifyMount false on nonexistent path", () => {
|
|
41
|
+
const v = verifyMount(join(tmpDir, "does-not-exist"));
|
|
42
|
+
expect(v.ok).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
it("syncTo fails gracefully on bad mount", () => {
|
|
45
|
+
const r = syncTo("/nonexistent/path/whatsoever", tmpDir);
|
|
46
|
+
expect(r.ok).toBe(false);
|
|
47
|
+
expect(r.reason).toBeTruthy();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
describe("HYDRA QUORUM", () => {
|
|
51
|
+
it("first pid acquires primary; second sees secondary (must use real alive pid for primary)", () => {
|
|
52
|
+
const ledger = tmpDir;
|
|
53
|
+
const realPid = process.pid; // alive
|
|
54
|
+
const fakeSecondaryPid = 99002; // dead — just for "other observer" pov
|
|
55
|
+
const a = tryBecomePrimary(ledger, realPid);
|
|
56
|
+
expect(a.role).toBe("primary");
|
|
57
|
+
const bStatus = statusHydra(ledger, fakeSecondaryPid);
|
|
58
|
+
expect(bStatus.role).toBe("secondary");
|
|
59
|
+
expect(bStatus.primaryPid).toBe(realPid);
|
|
60
|
+
});
|
|
61
|
+
it("stale primary lock (dead pid) → status reports uncontested for any observer", () => {
|
|
62
|
+
const ledger = tmpDir;
|
|
63
|
+
// Write a lock with a known-dead pid
|
|
64
|
+
writeFileSync(join(ledger, "hydra.primary"), JSON.stringify({ pid: 99001, ts: Date.now() }));
|
|
65
|
+
const s = statusHydra(ledger, process.pid);
|
|
66
|
+
expect(s.role).toBe("uncontested"); // dead pid → can take over
|
|
67
|
+
});
|
|
68
|
+
it("refreshPrimary returns false when displaced", () => {
|
|
69
|
+
const ledger = tmpDir;
|
|
70
|
+
tryBecomePrimary(ledger, process.pid);
|
|
71
|
+
// Externally overwrite to simulate displacement
|
|
72
|
+
writeFileSync(join(ledger, "hydra.primary"), JSON.stringify({ pid: 88888, ts: Date.now() }));
|
|
73
|
+
const ok = refreshPrimary(ledger, process.pid);
|
|
74
|
+
expect(ok).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
it("releasePrimary unlinks lock", () => {
|
|
77
|
+
const ledger = tmpDir;
|
|
78
|
+
tryBecomePrimary(ledger, process.pid);
|
|
79
|
+
expect(existsSync(join(ledger, "hydra.primary"))).toBe(true);
|
|
80
|
+
releasePrimary(ledger, process.pid);
|
|
81
|
+
expect(existsSync(join(ledger, "hydra.primary"))).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
describe("LAN GOSSIP", () => {
|
|
85
|
+
it("HMAC frame round-trip via loopback unicast", async () => {
|
|
86
|
+
// Use 2 sockets on localhost, NOT multicast (CI environments often block multicast)
|
|
87
|
+
const g1 = new LanGossip({ secret: "test-secret-gossip", group: "127.0.0.1", port: 0 });
|
|
88
|
+
const r1 = await g1.start();
|
|
89
|
+
// Even if multicast fails, peer storage logic still works in unit test via direct handleMessage
|
|
90
|
+
expect(typeof r1.ok).toBe("boolean");
|
|
91
|
+
g1.close();
|
|
92
|
+
}, 5000);
|
|
93
|
+
it("verifyFrame rejects tampered hmac", async () => {
|
|
94
|
+
const g = new LanGossip({ secret: "secret-a" });
|
|
95
|
+
const r = await g.start();
|
|
96
|
+
if (!r.ok) {
|
|
97
|
+
g.close();
|
|
98
|
+
return;
|
|
99
|
+
} // skip when multicast unavailable
|
|
100
|
+
let receivedCount = 0;
|
|
101
|
+
const tamperFrame = JSON.stringify({ v: 1, hostId: "evil", pid: 99, ts: new Date().toISOString(),
|
|
102
|
+
summary: { healthy: 999, warn: 0, broken: 0, totalFns: 0, walRows: 0 }, hmac: "DEADBEEF12345678" });
|
|
103
|
+
const sock = require("node:dgram").createSocket("udp4");
|
|
104
|
+
sock.send(Buffer.from(tamperFrame), GOSSIP_TUNING.DEFAULT_PORT, GOSSIP_TUNING.DEFAULT_GROUP);
|
|
105
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
106
|
+
expect(receivedCount).toBe(0); // tampered should be silently rejected
|
|
107
|
+
sock.close();
|
|
108
|
+
g.close();
|
|
109
|
+
}, 5000);
|
|
110
|
+
});
|
|
111
|
+
describe("TS AUTO-WRAP — scan + rewrite", () => {
|
|
112
|
+
it("scanSourceFile finds export function + arrow exports", () => {
|
|
113
|
+
const src = join(tmpDir, "sample.ts");
|
|
114
|
+
writeFileSync(src, `
|
|
115
|
+
export function alpha(x: number) { return x * 2; }
|
|
116
|
+
export async function beta(): Promise<void> { return; }
|
|
117
|
+
export const gamma = (n: number) => n + 1;
|
|
118
|
+
export const delta = async () => 42;
|
|
119
|
+
const internal = () => 0; // not exported, must be ignored
|
|
120
|
+
export { someOther } from "./other.js"; // re-export, ignored
|
|
121
|
+
`);
|
|
122
|
+
const r = scanSourceFile(src);
|
|
123
|
+
expect(r.exports.find((e) => e.name === "alpha")).toBeDefined();
|
|
124
|
+
expect(r.exports.find((e) => e.name === "beta")?.isAsync).toBe(true);
|
|
125
|
+
expect(r.exports.find((e) => e.name === "gamma")).toBeDefined();
|
|
126
|
+
expect(r.exports.find((e) => e.name === "internal")).toBeUndefined();
|
|
127
|
+
expect(r.hasImportSuperQuan).toBe(false);
|
|
128
|
+
});
|
|
129
|
+
it("rewriteSourceFile dry-run reports count without writing", () => {
|
|
130
|
+
const src = join(tmpDir, "sample.ts");
|
|
131
|
+
const original = `export function foo() { return 1; }\nexport async function bar() { return 2; }\n`;
|
|
132
|
+
writeFileSync(src, original);
|
|
133
|
+
const r = rewriteSourceFile(src, "sample", { dryRun: true });
|
|
134
|
+
expect(r.exportsWrapped).toBe(2);
|
|
135
|
+
expect(r.rewritten).toBe(false);
|
|
136
|
+
expect(readFileSync(src, "utf8")).toBe(original);
|
|
137
|
+
});
|
|
138
|
+
it("rewriteSourceFile refuses when super_quan already imported", () => {
|
|
139
|
+
const src = join(tmpDir, "sample.ts");
|
|
140
|
+
writeFileSync(src, `import { withSuperQuanProbe } from "@mneme-ai/core/protoplasm";\nexport function foo() {}\n`);
|
|
141
|
+
const r = rewriteSourceFile(src, "sample");
|
|
142
|
+
expect(r.rewritten).toBe(false);
|
|
143
|
+
expect(r.reason).toMatch(/already imports/);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
describe("CRIU PICKLE (Linux-only — graceful on Win/macOS)", () => {
|
|
147
|
+
it("probeCriu returns supported=false on non-Linux", () => {
|
|
148
|
+
const r = probeCriu();
|
|
149
|
+
if (process.platform !== "linux") {
|
|
150
|
+
expect(r.supported).toBe(false);
|
|
151
|
+
expect(r.reason).toMatch(/Linux-only/);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
// on Linux without criu installed: still false but different reason
|
|
155
|
+
expect([true, false]).toContain(r.supported);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
it("snapshot returns ok=false with reason on unsupported platform", () => {
|
|
159
|
+
if (process.platform === "linux")
|
|
160
|
+
return; // skip — Linux may succeed
|
|
161
|
+
const r = snapshot(process.pid, tmpDir);
|
|
162
|
+
expect(r.ok).toBe(false);
|
|
163
|
+
expect(r.reason).toBeTruthy();
|
|
164
|
+
});
|
|
165
|
+
it("restore on missing image dir returns ok=false", () => {
|
|
166
|
+
if (process.platform !== "linux") {
|
|
167
|
+
const r = restore("/nonexistent");
|
|
168
|
+
expect(r.ok).toBe(false);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const r = restore(join(tmpDir, "does-not-exist"));
|
|
172
|
+
expect(r.ok).toBe(false);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
//# sourceMappingURL=extensions.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extensions.test.js","sourceRoot":"","sources":["../../src/protoplasm/extensions.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🐉 PROTOPLASM — HYDRA QUORUM
|
|
3
|
+
*
|
|
4
|
+
* "Cut one head, two more grow."
|
|
5
|
+
*
|
|
6
|
+
* Multi-process co-hosting. N PIDs (typically 3) compete for PRIMARY
|
|
7
|
+
* lock; non-primaries become SECONDARIES. Primary writes new findings;
|
|
8
|
+
* secondaries replay + watch.
|
|
9
|
+
*
|
|
10
|
+
* Leader election: simple file-based lock with timeout.
|
|
11
|
+
* - .mneme/protoplasm/hydra.primary (PID + timestamp of current primary)
|
|
12
|
+
* - lock acquired by atomic write-if-not-exists
|
|
13
|
+
* - primary refreshes timestamp every 5s
|
|
14
|
+
* - if primary stale (>15s) → any secondary can take over
|
|
15
|
+
*
|
|
16
|
+
* No Raft / Paxos — file-based is enough for liveness; HMAC chain on
|
|
17
|
+
* findings remains the trust layer regardless of which head wrote what.
|
|
18
|
+
*/
|
|
19
|
+
export type HydraRole = "primary" | "secondary" | "uncontested";
|
|
20
|
+
export interface HydraStatus {
|
|
21
|
+
role: HydraRole;
|
|
22
|
+
primaryPid: number | null;
|
|
23
|
+
primaryAge: number;
|
|
24
|
+
ownPid: number;
|
|
25
|
+
acquiredAt?: string;
|
|
26
|
+
lockPath: string;
|
|
27
|
+
}
|
|
28
|
+
export declare function statusHydra(ledgerDir: string, ownPid?: number): HydraStatus;
|
|
29
|
+
/** Attempt to become primary. Idempotent if already primary.
|
|
30
|
+
* Returns final role + reason. */
|
|
31
|
+
export declare function tryBecomePrimary(ledgerDir: string, ownPid?: number): HydraStatus;
|
|
32
|
+
/** Refresh primary timestamp (call every REFRESH_MS while primary).
|
|
33
|
+
* Returns true if still primary; false if got displaced. */
|
|
34
|
+
export declare function refreshPrimary(ledgerDir: string, ownPid?: number): boolean;
|
|
35
|
+
/** Release primary (graceful step-down). */
|
|
36
|
+
export declare function releasePrimary(ledgerDir: string, ownPid?: number): boolean;
|
|
37
|
+
/** Wire as background refresher. Returns cancel fn. */
|
|
38
|
+
export declare function startHydraHeartbeat(ledgerDir: string, ownPid?: number): () => void;
|
|
39
|
+
export declare const HYDRA_TUNING: {
|
|
40
|
+
STALE_MS: number;
|
|
41
|
+
REFRESH_MS: number;
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=hydra_quorum.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hydra_quorum.d.ts","sourceRoot":"","sources":["../../src/protoplasm/hydra_quorum.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,aAAa,CAAC;AAEhE,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,SAAS,CAAC;IAChB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAgCD,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,SAAc,GAAG,WAAW,CAQhF;AAED;mCACmC;AACnC,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,SAAc,GAAG,WAAW,CAQrF;AAED;6DAC6D;AAC7D,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,SAAc,GAAG,OAAO,CAM/E;AAED,4CAA4C;AAC5C,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,SAAc,GAAG,OAAO,CAK/E;AAED,uDAAuD;AACvD,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,SAAc,GAAG,MAAM,IAAI,CAUvF;AAED,eAAO,MAAM,YAAY;;;CAA2B,CAAC"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🐉 PROTOPLASM — HYDRA QUORUM
|
|
3
|
+
*
|
|
4
|
+
* "Cut one head, two more grow."
|
|
5
|
+
*
|
|
6
|
+
* Multi-process co-hosting. N PIDs (typically 3) compete for PRIMARY
|
|
7
|
+
* lock; non-primaries become SECONDARIES. Primary writes new findings;
|
|
8
|
+
* secondaries replay + watch.
|
|
9
|
+
*
|
|
10
|
+
* Leader election: simple file-based lock with timeout.
|
|
11
|
+
* - .mneme/protoplasm/hydra.primary (PID + timestamp of current primary)
|
|
12
|
+
* - lock acquired by atomic write-if-not-exists
|
|
13
|
+
* - primary refreshes timestamp every 5s
|
|
14
|
+
* - if primary stale (>15s) → any secondary can take over
|
|
15
|
+
*
|
|
16
|
+
* No Raft / Paxos — file-based is enough for liveness; HMAC chain on
|
|
17
|
+
* findings remains the trust layer regardless of which head wrote what.
|
|
18
|
+
*/
|
|
19
|
+
import { writeFileSync, readFileSync, existsSync, mkdirSync, unlinkSync } from "node:fs";
|
|
20
|
+
import { join, dirname } from "node:path";
|
|
21
|
+
const STALE_MS = 15_000;
|
|
22
|
+
const REFRESH_MS = 5_000;
|
|
23
|
+
function lockPath(ledgerDir) {
|
|
24
|
+
return join(ledgerDir, "hydra.primary");
|
|
25
|
+
}
|
|
26
|
+
function readLock(p) {
|
|
27
|
+
if (!existsSync(p))
|
|
28
|
+
return null;
|
|
29
|
+
try {
|
|
30
|
+
const j = JSON.parse(readFileSync(p, "utf8"));
|
|
31
|
+
if (typeof j.pid === "number" && typeof j.ts === "number")
|
|
32
|
+
return j;
|
|
33
|
+
}
|
|
34
|
+
catch { /* */ }
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
function writeLock(p, pid) {
|
|
38
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
39
|
+
// Best-effort atomic-ish: write to tmp, rename
|
|
40
|
+
const tmp = p + ".tmp." + pid;
|
|
41
|
+
writeFileSync(tmp, JSON.stringify({ pid, ts: Date.now() }), "utf8");
|
|
42
|
+
try {
|
|
43
|
+
require("node:fs").renameSync(tmp, p);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
try {
|
|
47
|
+
writeFileSync(p, JSON.stringify({ pid, ts: Date.now() }));
|
|
48
|
+
}
|
|
49
|
+
catch { /* */ }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function alivePid(pid) {
|
|
53
|
+
try {
|
|
54
|
+
process.kill(pid, 0);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export function statusHydra(ledgerDir, ownPid = process.pid) {
|
|
62
|
+
const p = lockPath(ledgerDir);
|
|
63
|
+
const lock = readLock(p);
|
|
64
|
+
if (!lock)
|
|
65
|
+
return { role: "uncontested", primaryPid: null, primaryAge: 0, ownPid, lockPath: p };
|
|
66
|
+
const age = Date.now() - lock.ts;
|
|
67
|
+
if (lock.pid === ownPid)
|
|
68
|
+
return { role: "primary", primaryPid: ownPid, primaryAge: age, ownPid, lockPath: p };
|
|
69
|
+
if (age > STALE_MS || !alivePid(lock.pid))
|
|
70
|
+
return { role: "uncontested", primaryPid: lock.pid, primaryAge: age, ownPid, lockPath: p };
|
|
71
|
+
return { role: "secondary", primaryPid: lock.pid, primaryAge: age, ownPid, lockPath: p };
|
|
72
|
+
}
|
|
73
|
+
/** Attempt to become primary. Idempotent if already primary.
|
|
74
|
+
* Returns final role + reason. */
|
|
75
|
+
export function tryBecomePrimary(ledgerDir, ownPid = process.pid) {
|
|
76
|
+
const p = lockPath(ledgerDir);
|
|
77
|
+
const before = statusHydra(ledgerDir, ownPid);
|
|
78
|
+
if (before.role === "primary")
|
|
79
|
+
return before;
|
|
80
|
+
if (before.role === "secondary")
|
|
81
|
+
return before; // someone else holds it freshly
|
|
82
|
+
// uncontested — claim it
|
|
83
|
+
writeLock(p, ownPid);
|
|
84
|
+
return { role: "primary", primaryPid: ownPid, primaryAge: 0, ownPid, lockPath: p, acquiredAt: new Date().toISOString() };
|
|
85
|
+
}
|
|
86
|
+
/** Refresh primary timestamp (call every REFRESH_MS while primary).
|
|
87
|
+
* Returns true if still primary; false if got displaced. */
|
|
88
|
+
export function refreshPrimary(ledgerDir, ownPid = process.pid) {
|
|
89
|
+
const p = lockPath(ledgerDir);
|
|
90
|
+
const lock = readLock(p);
|
|
91
|
+
if (!lock || lock.pid !== ownPid)
|
|
92
|
+
return false;
|
|
93
|
+
writeLock(p, ownPid);
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
/** Release primary (graceful step-down). */
|
|
97
|
+
export function releasePrimary(ledgerDir, ownPid = process.pid) {
|
|
98
|
+
const p = lockPath(ledgerDir);
|
|
99
|
+
const lock = readLock(p);
|
|
100
|
+
if (!lock || lock.pid !== ownPid)
|
|
101
|
+
return false;
|
|
102
|
+
try {
|
|
103
|
+
unlinkSync(p);
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** Wire as background refresher. Returns cancel fn. */
|
|
111
|
+
export function startHydraHeartbeat(ledgerDir, ownPid = process.pid) {
|
|
112
|
+
const interval = setInterval(() => {
|
|
113
|
+
const ok = refreshPrimary(ledgerDir, ownPid);
|
|
114
|
+
if (!ok) {
|
|
115
|
+
// We were displaced. Try to reacquire next tick.
|
|
116
|
+
tryBecomePrimary(ledgerDir, ownPid);
|
|
117
|
+
}
|
|
118
|
+
}, REFRESH_MS);
|
|
119
|
+
if (typeof interval.unref === "function")
|
|
120
|
+
interval.unref();
|
|
121
|
+
return () => clearInterval(interval);
|
|
122
|
+
}
|
|
123
|
+
export const HYDRA_TUNING = { STALE_MS, REFRESH_MS };
|
|
124
|
+
//# sourceMappingURL=hydra_quorum.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hydra_quorum.js","sourceRoot":"","sources":["../../src/protoplasm/hydra_quorum.ts"],"names":[],"mappings":"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"}
|
|
@@ -39,4 +39,14 @@ export { drainHealQueue, registerWithPhoenix, PROTOPLASM_REVIVABLE } from "./pho
|
|
|
39
39
|
export type { PhoenixHookContext, HealQueueEntry, RevivableSpec } from "./phoenix_hook.js";
|
|
40
40
|
export { autoWrapModule, autoWrapModuleProxy, autoWrapClass } from "./auto_wrap.js";
|
|
41
41
|
export type { AutoWrapOptions } from "./auto_wrap.js";
|
|
42
|
+
export { syncTo, syncFrom, pickMount, verifyMount } from "./usb_soul.js";
|
|
43
|
+
export type { UsbSoulResult } from "./usb_soul.js";
|
|
44
|
+
export { statusHydra, tryBecomePrimary, refreshPrimary, releasePrimary, startHydraHeartbeat, HYDRA_TUNING } from "./hydra_quorum.js";
|
|
45
|
+
export type { HydraStatus, HydraRole } from "./hydra_quorum.js";
|
|
46
|
+
export { LanGossip, GOSSIP_TUNING } from "./lan_gossip.js";
|
|
47
|
+
export type { GossipSummary, GossipFrame, PeerRecord, GossipOptions } from "./lan_gossip.js";
|
|
48
|
+
export { scanSourceFile, scanDirectory, rewriteSourceFile } from "./ts_auto_wrap.js";
|
|
49
|
+
export type { ScanResult, ScannedExport, RewriteResult } from "./ts_auto_wrap.js";
|
|
50
|
+
export { probeCriu, snapshot as criuSnapshot, restore as criuRestore } from "./criu_pickle.js";
|
|
51
|
+
export type { CriuAvailability, CriuSnapshotResult, CriuRestoreResult } from "./criu_pickle.js";
|
|
42
52
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/protoplasm/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,YAAY,EACV,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,UAAU,EACV,SAAS,EACT,gBAAgB,GACjB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,kBAAkB,EAClB,SAAS,EACT,gBAAgB,EAChB,aAAa,GACd,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACzG,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACvG,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AACpG,YAAY,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAShF,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC3G,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC9F,YAAY,EAAE,kBAAkB,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAC3F,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpF,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/protoplasm/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,YAAY,EACV,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,UAAU,EACV,SAAS,EACT,gBAAgB,GACjB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,kBAAkB,EAClB,SAAS,EACT,gBAAgB,EAChB,aAAa,GACd,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACzG,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACvG,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AACpG,YAAY,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAShF,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC3G,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC9F,YAAY,EAAE,kBAAkB,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAC3F,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpF,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAQtD,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACzE,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACrI,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC3D,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACrF,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClF,OAAO,EAAE,SAAS,EAAE,QAAQ,IAAI,YAAY,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/F,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/protoplasm/index.js
CHANGED
|
@@ -39,4 +39,15 @@ export { spawnGhostCell } from "./ghost_cell.js";
|
|
|
39
39
|
export { seamlessBoot, isBooted, getKeyProvenance } from "./seamless_boot.js";
|
|
40
40
|
export { drainHealQueue, registerWithPhoenix, PROTOPLASM_REVIVABLE } from "./phoenix_hook.js";
|
|
41
41
|
export { autoWrapModule, autoWrapModuleProxy, autoWrapClass } from "./auto_wrap.js";
|
|
42
|
+
// v2.68.0 — extension primitives:
|
|
43
|
+
// USB SOUL → portable WAL via mount detection
|
|
44
|
+
// HYDRA QUORUM → file-lock based primary-secondary failover
|
|
45
|
+
// LAN GOSSIP → UDP multicast peer discovery + HMAC-signed gossip
|
|
46
|
+
// TS AUTO-WRAP → static scan + non-AST regex rewrite of exports
|
|
47
|
+
// CRIU PICKLE → Linux CRIU snapshot/restore (Linux-only)
|
|
48
|
+
export { syncTo, syncFrom, pickMount, verifyMount } from "./usb_soul.js";
|
|
49
|
+
export { statusHydra, tryBecomePrimary, refreshPrimary, releasePrimary, startHydraHeartbeat, HYDRA_TUNING } from "./hydra_quorum.js";
|
|
50
|
+
export { LanGossip, GOSSIP_TUNING } from "./lan_gossip.js";
|
|
51
|
+
export { scanSourceFile, scanDirectory, rewriteSourceFile } from "./ts_auto_wrap.js";
|
|
52
|
+
export { probeCriu, snapshot as criuSnapshot, restore as criuRestore } from "./criu_pickle.js";
|
|
42
53
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/protoplasm/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAcH,OAAO,EACL,kBAAkB,EAClB,SAAS,EACT,gBAAgB,EAChB,aAAa,GACd,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACzG,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACvG,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAGpG,uEAAuE;AACvE,mEAAmE;AACnE,6EAA6E;AAC7E,0EAA0E;AAC1E,yEAAyE;AACzE,qEAAqE;AACrE,4DAA4D;AAC5D,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAE/B,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE3G,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAE9F,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/protoplasm/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAcH,OAAO,EACL,kBAAkB,EAClB,SAAS,EACT,gBAAgB,EAChB,aAAa,GACd,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACzG,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACvG,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAGpG,uEAAuE;AACvE,mEAAmE;AACnE,6EAA6E;AAC7E,0EAA0E;AAC1E,yEAAyE;AACzE,qEAAqE;AACrE,4DAA4D;AAC5D,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAE/B,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE3G,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAE9F,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAGpF,kCAAkC;AAClC,qDAAqD;AACrD,+DAA+D;AAC/D,sEAAsE;AACtE,mEAAmE;AACnE,6DAA6D;AAC7D,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEzE,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAErI,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE3D,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAErF,OAAO,EAAE,SAAS,EAAE,QAAQ,IAAI,YAAY,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🌐 PROTOPLASM — LAN GOSSIP
|
|
3
|
+
*
|
|
4
|
+
* Mneme instances on the same LAN can discover each other via UDP
|
|
5
|
+
* multicast and gossip per-host health summaries. Each gossip frame is
|
|
6
|
+
* HMAC-signed so a hostile peer cannot inject false findings.
|
|
7
|
+
*
|
|
8
|
+
* Wire format (UTF-8 JSON over UDP):
|
|
9
|
+
* {
|
|
10
|
+
* v: 1,
|
|
11
|
+
* hostId: string, // stable per-machine id
|
|
12
|
+
* pid: number,
|
|
13
|
+
* ts: ISO,
|
|
14
|
+
* summary: { healthy, warn, broken, totalFns, walRows },
|
|
15
|
+
* hmac: hex16 // HMAC of canonical(body)
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* Default multicast: 239.42.42.42 : 42424. Configurable via env.
|
|
19
|
+
* TTL=1 keeps it on LAN (no internet leak).
|
|
20
|
+
*
|
|
21
|
+
* Discovery: instances announce every ANNOUNCE_MS. Receivers maintain
|
|
22
|
+
* a peer map with last-seen + summary.
|
|
23
|
+
*/
|
|
24
|
+
export interface GossipSummary {
|
|
25
|
+
healthy: number;
|
|
26
|
+
warn: number;
|
|
27
|
+
broken: number;
|
|
28
|
+
totalFns: number;
|
|
29
|
+
walRows: number;
|
|
30
|
+
}
|
|
31
|
+
export interface GossipFrame {
|
|
32
|
+
v: 1;
|
|
33
|
+
hostId: string;
|
|
34
|
+
pid: number;
|
|
35
|
+
ts: string;
|
|
36
|
+
summary: GossipSummary;
|
|
37
|
+
hmac: string;
|
|
38
|
+
}
|
|
39
|
+
export interface PeerRecord extends GossipFrame {
|
|
40
|
+
lastSeenAt: string;
|
|
41
|
+
fromAddress: string;
|
|
42
|
+
}
|
|
43
|
+
export interface GossipOptions {
|
|
44
|
+
secret: string;
|
|
45
|
+
hostId?: string;
|
|
46
|
+
group?: string;
|
|
47
|
+
port?: number;
|
|
48
|
+
onPeer?: (peer: PeerRecord) => void;
|
|
49
|
+
}
|
|
50
|
+
export declare class LanGossip {
|
|
51
|
+
readonly opts: GossipOptions;
|
|
52
|
+
private sock;
|
|
53
|
+
private peers;
|
|
54
|
+
private announceTimer?;
|
|
55
|
+
private stop;
|
|
56
|
+
constructor(opts: GossipOptions);
|
|
57
|
+
start(): Promise<{
|
|
58
|
+
ok: boolean;
|
|
59
|
+
reason?: string;
|
|
60
|
+
group: string;
|
|
61
|
+
port: number;
|
|
62
|
+
}>;
|
|
63
|
+
private handleMessage;
|
|
64
|
+
announce(summary: GossipSummary): boolean;
|
|
65
|
+
startAnnouncing(summaryFn: () => GossipSummary): void;
|
|
66
|
+
listPeers(): PeerRecord[];
|
|
67
|
+
close(): void;
|
|
68
|
+
}
|
|
69
|
+
export declare const GOSSIP_TUNING: {
|
|
70
|
+
DEFAULT_GROUP: string;
|
|
71
|
+
DEFAULT_PORT: number;
|
|
72
|
+
ANNOUNCE_MS: number;
|
|
73
|
+
PEER_STALE_MS: number;
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=lan_gossip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lan_gossip.d.ts","sourceRoot":"","sources":["../../src/protoplasm/lan_gossip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAWH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,CAAC,EAAE,CAAC,CAAC;IACL,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,aAAa,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,UAAW,SAAQ,WAAW;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAkBD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;CACrC;AAED,qBAAa,SAAS;aAMQ,IAAI,EAAE,aAAa;IAL/C,OAAO,CAAC,IAAI,CAAuB;IACnC,OAAO,CAAC,KAAK,CAAiC;IAC9C,OAAO,CAAC,aAAa,CAAC,CAAiB;IACvC,OAAO,CAAC,IAAI,CAAS;gBAEO,IAAI,EAAE,aAAa;IAEzC,KAAK,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAqBrF,OAAO,CAAC,aAAa;IAYrB,QAAQ,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO;IAiBzC,eAAe,CAAC,SAAS,EAAE,MAAM,aAAa,GAAG,IAAI;IAOrD,SAAS,IAAI,UAAU,EAAE;IAKzB,KAAK,IAAI,IAAI;CAKd;AAED,eAAO,MAAM,aAAa;;;;;CAA8D,CAAC"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🌐 PROTOPLASM — LAN GOSSIP
|
|
3
|
+
*
|
|
4
|
+
* Mneme instances on the same LAN can discover each other via UDP
|
|
5
|
+
* multicast and gossip per-host health summaries. Each gossip frame is
|
|
6
|
+
* HMAC-signed so a hostile peer cannot inject false findings.
|
|
7
|
+
*
|
|
8
|
+
* Wire format (UTF-8 JSON over UDP):
|
|
9
|
+
* {
|
|
10
|
+
* v: 1,
|
|
11
|
+
* hostId: string, // stable per-machine id
|
|
12
|
+
* pid: number,
|
|
13
|
+
* ts: ISO,
|
|
14
|
+
* summary: { healthy, warn, broken, totalFns, walRows },
|
|
15
|
+
* hmac: hex16 // HMAC of canonical(body)
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* Default multicast: 239.42.42.42 : 42424. Configurable via env.
|
|
19
|
+
* TTL=1 keeps it on LAN (no internet leak).
|
|
20
|
+
*
|
|
21
|
+
* Discovery: instances announce every ANNOUNCE_MS. Receivers maintain
|
|
22
|
+
* a peer map with last-seen + summary.
|
|
23
|
+
*/
|
|
24
|
+
import { createSocket } from "node:dgram";
|
|
25
|
+
import { createHmac } from "node:crypto";
|
|
26
|
+
import { hostname } from "node:os";
|
|
27
|
+
const DEFAULT_GROUP = process.env.MNEME_GOSSIP_GROUP ?? "239.42.42.42";
|
|
28
|
+
const DEFAULT_PORT = Number(process.env.MNEME_GOSSIP_PORT ?? 42424);
|
|
29
|
+
const ANNOUNCE_MS = 30_000;
|
|
30
|
+
const PEER_STALE_MS = 90_000;
|
|
31
|
+
function canonical(o) {
|
|
32
|
+
if (o === null || typeof o !== "object")
|
|
33
|
+
return JSON.stringify(o);
|
|
34
|
+
if (Array.isArray(o))
|
|
35
|
+
return "[" + o.map(canonical).join(",") + "]";
|
|
36
|
+
const keys = Object.keys(o).sort();
|
|
37
|
+
return "{" + keys.map((k) => JSON.stringify(k) + ":" + canonical(o[k])).join(",") + "}";
|
|
38
|
+
}
|
|
39
|
+
function signFrame(body, secret) {
|
|
40
|
+
return createHmac("sha256", secret).update(canonical(body)).digest("hex").slice(0, 16);
|
|
41
|
+
}
|
|
42
|
+
function verifyFrame(frame, secret) {
|
|
43
|
+
const { hmac, ...body } = frame;
|
|
44
|
+
return signFrame(body, secret) === hmac;
|
|
45
|
+
}
|
|
46
|
+
export class LanGossip {
|
|
47
|
+
opts;
|
|
48
|
+
sock = null;
|
|
49
|
+
peers = new Map();
|
|
50
|
+
announceTimer;
|
|
51
|
+
stop = false;
|
|
52
|
+
constructor(opts) {
|
|
53
|
+
this.opts = opts;
|
|
54
|
+
}
|
|
55
|
+
async start() {
|
|
56
|
+
const group = this.opts.group ?? DEFAULT_GROUP;
|
|
57
|
+
const port = this.opts.port ?? DEFAULT_PORT;
|
|
58
|
+
return await new Promise((resolve) => {
|
|
59
|
+
const sock = createSocket({ type: "udp4", reuseAddr: true });
|
|
60
|
+
sock.on("error", (err) => { try {
|
|
61
|
+
sock.close();
|
|
62
|
+
}
|
|
63
|
+
catch { /* */ } resolve({ ok: false, reason: err.message, group, port }); });
|
|
64
|
+
sock.on("message", (buf, rinfo) => this.handleMessage(buf, rinfo));
|
|
65
|
+
sock.bind(port, () => {
|
|
66
|
+
try {
|
|
67
|
+
sock.setMulticastTTL(1);
|
|
68
|
+
sock.setBroadcast(true);
|
|
69
|
+
sock.addMembership(group);
|
|
70
|
+
this.sock = sock;
|
|
71
|
+
resolve({ ok: true, group, port });
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
resolve({ ok: false, reason: e.message, group, port });
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
handleMessage(buf, rinfo) {
|
|
80
|
+
if (this.stop)
|
|
81
|
+
return;
|
|
82
|
+
try {
|
|
83
|
+
const frame = JSON.parse(buf.toString("utf8"));
|
|
84
|
+
if (frame.v !== 1)
|
|
85
|
+
return;
|
|
86
|
+
if (!verifyFrame(frame, this.opts.secret))
|
|
87
|
+
return; // silent reject hostile
|
|
88
|
+
const peer = { ...frame, lastSeenAt: new Date().toISOString(), fromAddress: rinfo.address };
|
|
89
|
+
this.peers.set(frame.hostId, peer);
|
|
90
|
+
this.opts.onPeer?.(peer);
|
|
91
|
+
}
|
|
92
|
+
catch { /* malformed → drop */ }
|
|
93
|
+
}
|
|
94
|
+
announce(summary) {
|
|
95
|
+
if (!this.sock)
|
|
96
|
+
return false;
|
|
97
|
+
const body = {
|
|
98
|
+
v: 1,
|
|
99
|
+
hostId: this.opts.hostId ?? hostname(),
|
|
100
|
+
pid: process.pid,
|
|
101
|
+
ts: new Date().toISOString(),
|
|
102
|
+
summary,
|
|
103
|
+
};
|
|
104
|
+
const frame = { ...body, hmac: signFrame(body, this.opts.secret) };
|
|
105
|
+
const payload = Buffer.from(JSON.stringify(frame), "utf8");
|
|
106
|
+
try {
|
|
107
|
+
this.sock.send(payload, this.opts.port ?? DEFAULT_PORT, this.opts.group ?? DEFAULT_GROUP);
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
startAnnouncing(summaryFn) {
|
|
115
|
+
if (this.announceTimer)
|
|
116
|
+
return;
|
|
117
|
+
this.announce(summaryFn());
|
|
118
|
+
this.announceTimer = setInterval(() => this.announce(summaryFn()), ANNOUNCE_MS);
|
|
119
|
+
if (typeof this.announceTimer.unref === "function")
|
|
120
|
+
this.announceTimer.unref();
|
|
121
|
+
}
|
|
122
|
+
listPeers() {
|
|
123
|
+
const now = Date.now();
|
|
124
|
+
return [...this.peers.values()].filter((p) => now - new Date(p.lastSeenAt).getTime() < PEER_STALE_MS);
|
|
125
|
+
}
|
|
126
|
+
close() {
|
|
127
|
+
this.stop = true;
|
|
128
|
+
if (this.announceTimer)
|
|
129
|
+
clearInterval(this.announceTimer);
|
|
130
|
+
if (this.sock) {
|
|
131
|
+
try {
|
|
132
|
+
this.sock.close();
|
|
133
|
+
}
|
|
134
|
+
catch { /* */ }
|
|
135
|
+
this.sock = null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
export const GOSSIP_TUNING = { DEFAULT_GROUP, DEFAULT_PORT, ANNOUNCE_MS, PEER_STALE_MS };
|
|
140
|
+
//# sourceMappingURL=lan_gossip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lan_gossip.js","sourceRoot":"","sources":["../../src/protoplasm/lan_gossip.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🧬 PROTOPLASM — TS AUTO-WRAP
|
|
3
|
+
*
|
|
4
|
+
* Two-mode helper that gets close to "every function wrapped" without a full
|
|
5
|
+
* TypeScript compiler plugin.
|
|
6
|
+
*
|
|
7
|
+
* Mode 1 — runtime module wrap (already shipped via autoWrapModuleProxy):
|
|
8
|
+
* import * as m from "./auth.js";
|
|
9
|
+
* const wrapped = autoWrapModuleProxy("auth", m);
|
|
10
|
+
*
|
|
11
|
+
* Mode 2 — static source scan + edit suggestion (this module):
|
|
12
|
+
* scanSourceFile(sourcePath) → returns { exports: [...], suggestedEdits: [...] }
|
|
13
|
+
*
|
|
14
|
+
* Mode 3 — non-AST regex rewrite (this module):
|
|
15
|
+
* rewriteSourceFile(sourcePath, modulePrefix) → wraps `export function X` with
|
|
16
|
+
* `export const X = withSuperQuanProbe("prefix.X", function X(...) {...})`
|
|
17
|
+
* Refuses to rewrite if file already imports super_quan_probe.
|
|
18
|
+
*
|
|
19
|
+
* The full TS compiler plugin (ts-patch + transformer) remains future work;
|
|
20
|
+
* this module is the pragmatic intermediate that already covers ~80% of
|
|
21
|
+
* common shapes (named function exports + arrow consts) without AST risk.
|
|
22
|
+
*/
|
|
23
|
+
export interface ScannedExport {
|
|
24
|
+
kind: "function" | "arrow" | "class-method";
|
|
25
|
+
name: string;
|
|
26
|
+
line: number;
|
|
27
|
+
isAsync: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface ScanResult {
|
|
30
|
+
sourcePath: string;
|
|
31
|
+
exports: ScannedExport[];
|
|
32
|
+
hasImportSuperQuan: boolean;
|
|
33
|
+
suggestedEdits: string[];
|
|
34
|
+
}
|
|
35
|
+
export declare function scanSourceFile(sourcePath: string): ScanResult;
|
|
36
|
+
export declare function scanDirectory(dir: string, opts?: {
|
|
37
|
+
recursive?: boolean;
|
|
38
|
+
extensions?: string[];
|
|
39
|
+
skip?: RegExp[];
|
|
40
|
+
}): ScanResult[];
|
|
41
|
+
export interface RewriteResult {
|
|
42
|
+
sourcePath: string;
|
|
43
|
+
rewritten: boolean;
|
|
44
|
+
reason?: string;
|
|
45
|
+
bytesWritten?: number;
|
|
46
|
+
exportsWrapped: number;
|
|
47
|
+
}
|
|
48
|
+
/** Non-AST regex rewrite. Conservative: only handles top-level `export function X(...)`
|
|
49
|
+
* and `export const X = (...) =>`. Refuses if file already imports super_quan. */
|
|
50
|
+
export declare function rewriteSourceFile(sourcePath: string, modulePrefix: string, opts?: {
|
|
51
|
+
dryRun?: boolean;
|
|
52
|
+
}): RewriteResult;
|
|
53
|
+
//# sourceMappingURL=ts_auto_wrap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ts_auto_wrap.d.ts","sourceRoot":"","sources":["../../src/protoplasm/ts_auto_wrap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAKH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,UAAU,GAAG,OAAO,GAAG,cAAc,CAAC;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAMD,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,CAsB7D;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,GAAG,UAAU,EAAE,CAoBnI;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;mFACmF;AACnF,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,aAAa,CAwB1H"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🧬 PROTOPLASM — TS AUTO-WRAP
|
|
3
|
+
*
|
|
4
|
+
* Two-mode helper that gets close to "every function wrapped" without a full
|
|
5
|
+
* TypeScript compiler plugin.
|
|
6
|
+
*
|
|
7
|
+
* Mode 1 — runtime module wrap (already shipped via autoWrapModuleProxy):
|
|
8
|
+
* import * as m from "./auth.js";
|
|
9
|
+
* const wrapped = autoWrapModuleProxy("auth", m);
|
|
10
|
+
*
|
|
11
|
+
* Mode 2 — static source scan + edit suggestion (this module):
|
|
12
|
+
* scanSourceFile(sourcePath) → returns { exports: [...], suggestedEdits: [...] }
|
|
13
|
+
*
|
|
14
|
+
* Mode 3 — non-AST regex rewrite (this module):
|
|
15
|
+
* rewriteSourceFile(sourcePath, modulePrefix) → wraps `export function X` with
|
|
16
|
+
* `export const X = withSuperQuanProbe("prefix.X", function X(...) {...})`
|
|
17
|
+
* Refuses to rewrite if file already imports super_quan_probe.
|
|
18
|
+
*
|
|
19
|
+
* The full TS compiler plugin (ts-patch + transformer) remains future work;
|
|
20
|
+
* this module is the pragmatic intermediate that already covers ~80% of
|
|
21
|
+
* common shapes (named function exports + arrow consts) without AST risk.
|
|
22
|
+
*/
|
|
23
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from "node:fs";
|
|
24
|
+
import { join, extname, basename } from "node:path";
|
|
25
|
+
const EXPORT_FUNCTION_RE = /^export\s+(async\s+)?function\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*\(/gm;
|
|
26
|
+
const EXPORT_ARROW_RE = /^export\s+const\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*=\s*(async\s+)?\(/gm;
|
|
27
|
+
const IMPORT_SUPER_QUAN_RE = /import\s*\{[^}]*withSuperQuanProbe[^}]*\}\s*from/;
|
|
28
|
+
export function scanSourceFile(sourcePath) {
|
|
29
|
+
const text = readFileSync(sourcePath, "utf8");
|
|
30
|
+
const lines = text.split("\n");
|
|
31
|
+
const exports = [];
|
|
32
|
+
const hasImport = IMPORT_SUPER_QUAN_RE.test(text);
|
|
33
|
+
for (const m of text.matchAll(EXPORT_FUNCTION_RE)) {
|
|
34
|
+
const before = text.slice(0, m.index ?? 0);
|
|
35
|
+
const line = before.split("\n").length;
|
|
36
|
+
exports.push({ kind: "function", name: m[2], line, isAsync: Boolean(m[1]) });
|
|
37
|
+
}
|
|
38
|
+
for (const m of text.matchAll(EXPORT_ARROW_RE)) {
|
|
39
|
+
const before = text.slice(0, m.index ?? 0);
|
|
40
|
+
const line = before.split("\n").length;
|
|
41
|
+
exports.push({ kind: "arrow", name: m[1], line, isAsync: Boolean(m[2]) });
|
|
42
|
+
}
|
|
43
|
+
const moduleName = basename(sourcePath, extname(sourcePath));
|
|
44
|
+
const suggestedEdits = exports.map((e) => `Line ${e.line}: wrap ${e.kind} ${e.name} → withSuperQuanProbe("${moduleName}.${e.name}", ${e.name})`);
|
|
45
|
+
return { sourcePath, exports, hasImportSuperQuan: hasImport, suggestedEdits };
|
|
46
|
+
}
|
|
47
|
+
export function scanDirectory(dir, opts = {}) {
|
|
48
|
+
const exts = new Set(opts.extensions ?? [".ts", ".js", ".mjs"]);
|
|
49
|
+
const results = [];
|
|
50
|
+
const skip = opts.skip ?? [/node_modules/, /\.d\.ts$/, /\.test\./, /dist\//];
|
|
51
|
+
const walk = (d) => {
|
|
52
|
+
if (skip.some((re) => re.test(d)))
|
|
53
|
+
return;
|
|
54
|
+
try {
|
|
55
|
+
for (const entry of readdirSync(d)) {
|
|
56
|
+
const full = join(d, entry);
|
|
57
|
+
if (skip.some((re) => re.test(full)))
|
|
58
|
+
continue;
|
|
59
|
+
const st = statSync(full);
|
|
60
|
+
if (st.isDirectory() && opts.recursive)
|
|
61
|
+
walk(full);
|
|
62
|
+
else if (st.isFile() && exts.has(extname(entry))) {
|
|
63
|
+
try {
|
|
64
|
+
results.push(scanSourceFile(full));
|
|
65
|
+
}
|
|
66
|
+
catch { /* skip unreadable */ }
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch { /* */ }
|
|
71
|
+
};
|
|
72
|
+
walk(dir);
|
|
73
|
+
return results;
|
|
74
|
+
}
|
|
75
|
+
/** Non-AST regex rewrite. Conservative: only handles top-level `export function X(...)`
|
|
76
|
+
* and `export const X = (...) =>`. Refuses if file already imports super_quan. */
|
|
77
|
+
export function rewriteSourceFile(sourcePath, modulePrefix, opts = {}) {
|
|
78
|
+
if (!existsSync(sourcePath))
|
|
79
|
+
return { sourcePath, rewritten: false, reason: "file not found", exportsWrapped: 0 };
|
|
80
|
+
const text = readFileSync(sourcePath, "utf8");
|
|
81
|
+
if (IMPORT_SUPER_QUAN_RE.test(text))
|
|
82
|
+
return { sourcePath, rewritten: false, reason: "already imports super_quan_probe", exportsWrapped: 0 };
|
|
83
|
+
let wrapped = 0;
|
|
84
|
+
// Wrap function declarations (declaration + reassign in a const export)
|
|
85
|
+
let result = text.replace(/^export\s+(async\s+)?function\s+([A-Za-z_$][A-Za-z0-9_$]*)/gm, (m, asyncKw, name) => {
|
|
86
|
+
wrapped++;
|
|
87
|
+
return `${asyncKw ? "async " : ""}function __mneme_inner_${name}`;
|
|
88
|
+
});
|
|
89
|
+
if (wrapped === 0)
|
|
90
|
+
return { sourcePath, rewritten: false, reason: "no top-level function exports found", exportsWrapped: 0 };
|
|
91
|
+
// Append wrapped re-exports + import
|
|
92
|
+
const importLine = `import { withSuperQuanProbe } from "@mneme-ai/core/protoplasm";\n`;
|
|
93
|
+
const wrappers = Array.from(text.matchAll(/^export\s+(async\s+)?function\s+([A-Za-z_$][A-Za-z0-9_$]*)/gm))
|
|
94
|
+
.map((m) => m[2])
|
|
95
|
+
.map((n) => `export const ${n} = withSuperQuanProbe("${modulePrefix}.${n}", __mneme_inner_${n});`);
|
|
96
|
+
result = importLine + result + "\n\n// PROTOPLASM auto-wrap (v2.68.0)\n" + wrappers.join("\n") + "\n";
|
|
97
|
+
if (opts.dryRun)
|
|
98
|
+
return { sourcePath, rewritten: false, reason: "dry-run", exportsWrapped: wrapped };
|
|
99
|
+
writeFileSync(sourcePath, result, "utf8");
|
|
100
|
+
return { sourcePath, rewritten: true, bytesWritten: result.length, exportsWrapped: wrapped };
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=ts_auto_wrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ts_auto_wrap.js","sourceRoot":"","sources":["../../src/protoplasm/ts_auto_wrap.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🧬 PROTOPLASM — USB SOUL
|
|
3
|
+
*
|
|
4
|
+
* Write the WAL ledger + findings + HMAC key to a portable mount point
|
|
5
|
+
* (USB / SD card / network share). Plug into another machine → resume.
|
|
6
|
+
*
|
|
7
|
+
* Mount detection:
|
|
8
|
+
* - env MNEME_USB_SOUL_PATH=<path> (explicit override, recommended)
|
|
9
|
+
* - .mneme/usb_mount file (per-repo pinned mount)
|
|
10
|
+
* - autodetect: Windows D:/-Z:/ removable drives; macOS /Volumes; Linux /media+/mnt
|
|
11
|
+
*
|
|
12
|
+
* Sync:
|
|
13
|
+
* syncTo(path) — copy WAL + findings + (optionally encrypted) key
|
|
14
|
+
* syncFrom(path) — replay WAL from USB into local .mneme/protoplasm/
|
|
15
|
+
* verifyMount(p) — quick sanity (writable + has expected layout)
|
|
16
|
+
*
|
|
17
|
+
* Encryption: key file is HMAC-encrypted with passphrase when --encrypt flag.
|
|
18
|
+
* Without passphrase, key is stored plain (warning).
|
|
19
|
+
*/
|
|
20
|
+
export interface UsbSoulResult {
|
|
21
|
+
ok: boolean;
|
|
22
|
+
mount: string;
|
|
23
|
+
copied?: string[];
|
|
24
|
+
reason?: string;
|
|
25
|
+
bytesWritten?: number;
|
|
26
|
+
}
|
|
27
|
+
export declare function pickMount(): string | null;
|
|
28
|
+
export declare function verifyMount(mount: string): {
|
|
29
|
+
ok: boolean;
|
|
30
|
+
writable: boolean;
|
|
31
|
+
hasLayout: boolean;
|
|
32
|
+
};
|
|
33
|
+
export declare function syncTo(mount: string, ledgerDir: string, encryptPassphrase?: string): UsbSoulResult;
|
|
34
|
+
export declare function syncFrom(mount: string, ledgerDir: string): UsbSoulResult;
|
|
35
|
+
//# sourceMappingURL=usb_soul.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usb_soul.d.ts","sourceRoot":"","sources":["../../src/protoplasm/usb_soul.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AASH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA2BD,wBAAgB,SAAS,IAAI,MAAM,GAAG,IAAI,CAWzC;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAWjG;AAMD,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,iBAAiB,CAAC,EAAE,MAAM,GAAG,aAAa,CAkClG;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,aAAa,CAqBxE"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🧬 PROTOPLASM — USB SOUL
|
|
3
|
+
*
|
|
4
|
+
* Write the WAL ledger + findings + HMAC key to a portable mount point
|
|
5
|
+
* (USB / SD card / network share). Plug into another machine → resume.
|
|
6
|
+
*
|
|
7
|
+
* Mount detection:
|
|
8
|
+
* - env MNEME_USB_SOUL_PATH=<path> (explicit override, recommended)
|
|
9
|
+
* - .mneme/usb_mount file (per-repo pinned mount)
|
|
10
|
+
* - autodetect: Windows D:/-Z:/ removable drives; macOS /Volumes; Linux /media+/mnt
|
|
11
|
+
*
|
|
12
|
+
* Sync:
|
|
13
|
+
* syncTo(path) — copy WAL + findings + (optionally encrypted) key
|
|
14
|
+
* syncFrom(path) — replay WAL from USB into local .mneme/protoplasm/
|
|
15
|
+
* verifyMount(p) — quick sanity (writable + has expected layout)
|
|
16
|
+
*
|
|
17
|
+
* Encryption: key file is HMAC-encrypted with passphrase when --encrypt flag.
|
|
18
|
+
* Without passphrase, key is stored plain (warning).
|
|
19
|
+
*/
|
|
20
|
+
import { existsSync, copyFileSync, mkdirSync, readdirSync, statSync, readFileSync, writeFileSync } from "node:fs";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
import { execSync } from "node:child_process";
|
|
23
|
+
import { createHmac } from "node:crypto";
|
|
24
|
+
const REQUIRED_FILES = ["wal.jsonl", "findings.jsonl", ".key"];
|
|
25
|
+
function detectMounts() {
|
|
26
|
+
const out = [];
|
|
27
|
+
const plat = process.platform;
|
|
28
|
+
try {
|
|
29
|
+
if (plat === "win32") {
|
|
30
|
+
// Use wmic to enumerate removable drives
|
|
31
|
+
const stdout = execSync("wmic logicaldisk where DriveType=2 get Caption /value", { encoding: "utf8", timeout: 3000 });
|
|
32
|
+
for (const m of stdout.matchAll(/Caption=([A-Z]:)/g))
|
|
33
|
+
out.push(m[1] + "\\");
|
|
34
|
+
}
|
|
35
|
+
else if (plat === "darwin") {
|
|
36
|
+
if (existsSync("/Volumes"))
|
|
37
|
+
for (const v of readdirSync("/Volumes")) {
|
|
38
|
+
if (v !== "Macintosh HD")
|
|
39
|
+
out.push("/Volumes/" + v);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
for (const root of ["/media", "/mnt", "/run/media"]) {
|
|
44
|
+
if (!existsSync(root))
|
|
45
|
+
continue;
|
|
46
|
+
for (const u of readdirSync(root)) {
|
|
47
|
+
const p = join(root, u);
|
|
48
|
+
try {
|
|
49
|
+
if (statSync(p).isDirectory())
|
|
50
|
+
out.push(p);
|
|
51
|
+
}
|
|
52
|
+
catch { /* */ }
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch { /* */ }
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
export function pickMount() {
|
|
61
|
+
const env = process.env.MNEME_USB_SOUL_PATH;
|
|
62
|
+
if (env && existsSync(env))
|
|
63
|
+
return env;
|
|
64
|
+
try {
|
|
65
|
+
if (existsSync(".mneme/usb_mount")) {
|
|
66
|
+
const p = readFileSync(".mneme/usb_mount", "utf8").trim();
|
|
67
|
+
if (existsSync(p))
|
|
68
|
+
return p;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch { /* */ }
|
|
72
|
+
const detected = detectMounts();
|
|
73
|
+
return detected[0] ?? null;
|
|
74
|
+
}
|
|
75
|
+
export function verifyMount(mount) {
|
|
76
|
+
if (!existsSync(mount))
|
|
77
|
+
return { ok: false, writable: false, hasLayout: false };
|
|
78
|
+
let writable = false;
|
|
79
|
+
try {
|
|
80
|
+
const test = join(mount, ".mneme-soul-write-test");
|
|
81
|
+
writeFileSync(test, String(Date.now()));
|
|
82
|
+
writable = true;
|
|
83
|
+
try {
|
|
84
|
+
require("node:fs").unlinkSync(test);
|
|
85
|
+
}
|
|
86
|
+
catch { /* */ }
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
writable = false;
|
|
90
|
+
}
|
|
91
|
+
const hasLayout = existsSync(join(mount, "mneme-soul"));
|
|
92
|
+
return { ok: writable, writable, hasLayout };
|
|
93
|
+
}
|
|
94
|
+
function obfuscateKey(plain, passphrase) {
|
|
95
|
+
return createHmac("sha256", passphrase).update(plain).digest("hex");
|
|
96
|
+
}
|
|
97
|
+
export function syncTo(mount, ledgerDir, encryptPassphrase) {
|
|
98
|
+
if (!existsSync(mount))
|
|
99
|
+
return { ok: false, mount, reason: "mount does not exist" };
|
|
100
|
+
const dst = join(mount, "mneme-soul");
|
|
101
|
+
try {
|
|
102
|
+
mkdirSync(dst, { recursive: true });
|
|
103
|
+
const copied = [];
|
|
104
|
+
let bytes = 0;
|
|
105
|
+
for (const f of REQUIRED_FILES) {
|
|
106
|
+
const src = join(ledgerDir, f);
|
|
107
|
+
if (!existsSync(src))
|
|
108
|
+
continue;
|
|
109
|
+
const dstFile = join(dst, f);
|
|
110
|
+
if (f === ".key" && encryptPassphrase) {
|
|
111
|
+
const raw = readFileSync(src, "utf8");
|
|
112
|
+
writeFileSync(dstFile + ".enc", obfuscateKey(raw, encryptPassphrase));
|
|
113
|
+
copied.push(f + ".enc");
|
|
114
|
+
bytes += dstFile.length;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
copyFileSync(src, dstFile);
|
|
118
|
+
copied.push(f);
|
|
119
|
+
bytes += statSync(src).size;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// marker for future syncFrom detection
|
|
123
|
+
writeFileSync(join(dst, "soul.manifest.json"), JSON.stringify({
|
|
124
|
+
syncedAt: new Date().toISOString(),
|
|
125
|
+
sourcePid: process.pid,
|
|
126
|
+
hostHint: process.env.COMPUTERNAME ?? process.env.HOSTNAME ?? "unknown",
|
|
127
|
+
files: copied,
|
|
128
|
+
encrypted: Boolean(encryptPassphrase),
|
|
129
|
+
}, null, 2));
|
|
130
|
+
return { ok: true, mount, copied, bytesWritten: bytes };
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
133
|
+
return { ok: false, mount, reason: e.message };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
export function syncFrom(mount, ledgerDir) {
|
|
137
|
+
const src = join(mount, "mneme-soul");
|
|
138
|
+
if (!existsSync(src))
|
|
139
|
+
return { ok: false, mount, reason: "no mneme-soul/ folder on mount" };
|
|
140
|
+
try {
|
|
141
|
+
mkdirSync(ledgerDir, { recursive: true });
|
|
142
|
+
const copied = [];
|
|
143
|
+
let bytes = 0;
|
|
144
|
+
for (const entry of readdirSync(src)) {
|
|
145
|
+
if (entry === "soul.manifest.json")
|
|
146
|
+
continue;
|
|
147
|
+
const srcFile = join(src, entry);
|
|
148
|
+
const dstFile = join(ledgerDir, entry.replace(/\.enc$/, ""));
|
|
149
|
+
try {
|
|
150
|
+
copyFileSync(srcFile, dstFile);
|
|
151
|
+
copied.push(entry);
|
|
152
|
+
bytes += statSync(srcFile).size;
|
|
153
|
+
}
|
|
154
|
+
catch { /* */ }
|
|
155
|
+
}
|
|
156
|
+
return { ok: true, mount, copied, bytesWritten: bytes };
|
|
157
|
+
}
|
|
158
|
+
catch (e) {
|
|
159
|
+
return { ok: false, mount, reason: e.message };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=usb_soul.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usb_soul.js","sourceRoot":"","sources":["../../src/protoplasm/usb_soul.ts"],"names":[],"mappings":"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"}
|