@mneme-ai/core 2.19.29 → 2.19.31

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 (59) hide show
  1. package/dist/beacon/beacon.test.js +44 -0
  2. package/dist/beacon/beacon.test.js.map +1 -1
  3. package/dist/beacon/index.d.ts.map +1 -1
  4. package/dist/beacon/index.js +5 -1
  5. package/dist/beacon/index.js.map +1 -1
  6. package/dist/conversation_compiler/conversation_compiler.test.js +17 -0
  7. package/dist/conversation_compiler/conversation_compiler.test.js.map +1 -1
  8. package/dist/conversation_compiler/index.d.ts.map +1 -1
  9. package/dist/conversation_compiler/index.js +28 -16
  10. package/dist/conversation_compiler/index.js.map +1 -1
  11. package/dist/cosmic/aurelian_v1930.test.d.ts +2 -0
  12. package/dist/cosmic/aurelian_v1930.test.d.ts.map +1 -0
  13. package/dist/cosmic/aurelian_v1930.test.js +61 -0
  14. package/dist/cosmic/aurelian_v1930.test.js.map +1 -0
  15. package/dist/cosmic/aurelian_v1931.test.d.ts +2 -0
  16. package/dist/cosmic/aurelian_v1931.test.d.ts.map +1 -0
  17. package/dist/cosmic/aurelian_v1931.test.js +67 -0
  18. package/dist/cosmic/aurelian_v1931.test.js.map +1 -0
  19. package/dist/hive_court/hive_court.test.d.ts +2 -0
  20. package/dist/hive_court/hive_court.test.d.ts.map +1 -0
  21. package/dist/hive_court/hive_court.test.js +193 -0
  22. package/dist/hive_court/hive_court.test.js.map +1 -0
  23. package/dist/hive_court/index.d.ts +126 -0
  24. package/dist/hive_court/index.d.ts.map +1 -0
  25. package/dist/hive_court/index.js +195 -0
  26. package/dist/hive_court/index.js.map +1 -0
  27. package/dist/index.d.ts +3 -0
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +7 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/soul_embalming/index.d.ts +127 -0
  32. package/dist/soul_embalming/index.d.ts.map +1 -0
  33. package/dist/soul_embalming/index.js +184 -0
  34. package/dist/soul_embalming/index.js.map +1 -0
  35. package/dist/soul_embalming/soul_embalming.test.d.ts +2 -0
  36. package/dist/soul_embalming/soul_embalming.test.d.ts.map +1 -0
  37. package/dist/soul_embalming/soul_embalming.test.js +171 -0
  38. package/dist/soul_embalming/soul_embalming.test.js.map +1 -0
  39. package/dist/synapse_sync/index.d.ts +142 -0
  40. package/dist/synapse_sync/index.d.ts.map +1 -0
  41. package/dist/synapse_sync/index.js +323 -0
  42. package/dist/synapse_sync/index.js.map +1 -0
  43. package/dist/synapse_sync/synapse_sync.test.d.ts +2 -0
  44. package/dist/synapse_sync/synapse_sync.test.d.ts.map +1 -0
  45. package/dist/synapse_sync/synapse_sync.test.js +363 -0
  46. package/dist/synapse_sync/synapse_sync.test.js.map +1 -0
  47. package/dist/truth_forensic_pipeline/index.d.ts +24 -0
  48. package/dist/truth_forensic_pipeline/index.d.ts.map +1 -1
  49. package/dist/truth_forensic_pipeline/index.js +110 -4
  50. package/dist/truth_forensic_pipeline/index.js.map +1 -1
  51. package/dist/truth_forensic_pipeline/truth_forensic_pipeline.test.js +83 -0
  52. package/dist/truth_forensic_pipeline/truth_forensic_pipeline.test.js.map +1 -1
  53. package/dist/whats_new.d.ts.map +1 -1
  54. package/dist/whats_new.js +16 -0
  55. package/dist/whats_new.js.map +1 -1
  56. package/dist/wrapper_genesis/index.d.ts.map +1 -1
  57. package/dist/wrapper_genesis/index.js +21 -0
  58. package/dist/wrapper_genesis/index.js.map +1 -1
  59. package/package.json +1 -1
@@ -0,0 +1,171 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { emptyCrypt, embalmSoul, restoreLatestSoul, restoreSoulAt, verifyCrypt, computeCryptStats, formatCryptLine, SOUL_EMBALMING_TUNABLES, } from "./index.js";
3
+ const SECRET = "soul-test-secret-997744";
4
+ function mkSoul(agentId, ts, goal = "x") {
5
+ return {
6
+ v: 1,
7
+ agentId,
8
+ vendorAtEmbalm: "claude",
9
+ currentGoal: goal,
10
+ decisionHistory: [{ summary: "decided x", ts, outcome: "merged" }],
11
+ mentalModel: { fact: "y" },
12
+ currentBiases: { focus: 0.7, fatigue: 0.2 },
13
+ lastToolCalls: [{ toolName: "mneme.ask", ok: true, ts }],
14
+ embalmedAtMs: ts,
15
+ };
16
+ }
17
+ describe("v2.19.30 SOUL EMBALMING · core", () => {
18
+ it("emptyCrypt has 0 records + correct agent id + default ring size", () => {
19
+ const c = emptyCrypt("agent-A");
20
+ expect(c.records.length).toBe(0);
21
+ expect(c.agentId).toBe("agent-A");
22
+ expect(c.ringBufferSize).toBe(SOUL_EMBALMING_TUNABLES.DEFAULT_RING_BUFFER_SIZE);
23
+ });
24
+ it("embalmSoul appends + HMAC-chains to predecessor", () => {
25
+ let c = emptyCrypt("a");
26
+ c = embalmSoul({ crypt: c, soul: mkSoul("a", 1), secret: SECRET });
27
+ c = embalmSoul({ crypt: c, soul: mkSoul("a", 2), secret: SECRET });
28
+ expect(c.records.length).toBe(2);
29
+ expect(c.records[1].prevSig).toBe(c.records[0].sig);
30
+ expect(verifyCrypt(c, SECRET)).toBe(true);
31
+ });
32
+ it("DEFENSIVE: soul with mismatched agentId is rejected (returns crypt unchanged)", () => {
33
+ const c0 = emptyCrypt("a");
34
+ const c1 = embalmSoul({ crypt: c0, soul: mkSoul("WRONG-AGENT", 1), secret: SECRET });
35
+ expect(c1.records.length).toBe(0);
36
+ });
37
+ it("DEFENSIVE: soul missing agentId is rejected", () => {
38
+ const c0 = emptyCrypt("a");
39
+ const malformed = { ...mkSoul("a", 1), agentId: "" };
40
+ const c1 = embalmSoul({ crypt: c0, soul: malformed, secret: SECRET });
41
+ expect(c1.records.length).toBe(0);
42
+ });
43
+ it("ring buffer evicts oldest when exceeded", () => {
44
+ let c = emptyCrypt("a", 3); // tiny ring for test
45
+ for (let i = 0; i < 5; i++) {
46
+ c = embalmSoul({ crypt: c, soul: mkSoul("a", i), secret: SECRET });
47
+ }
48
+ expect(c.records.length).toBe(3);
49
+ expect(c.records[0].soul.embalmedAtMs).toBe(2); // oldest 3 evicted; indices 2,3,4 remain
50
+ expect(c.records[2].soul.embalmedAtMs).toBe(4);
51
+ });
52
+ it("decisionHistory + lastToolCalls capped to defaults (no unbounded growth)", () => {
53
+ const big = {
54
+ ...mkSoul("a", 1),
55
+ decisionHistory: Array.from({ length: 500 }, (_, i) => ({ summary: `d${i}`, ts: i, outcome: "merged" })),
56
+ lastToolCalls: Array.from({ length: 100 }, (_, i) => ({ toolName: `t${i}`, ok: true, ts: i })),
57
+ };
58
+ const c = embalmSoul({ crypt: emptyCrypt("a"), soul: big, secret: SECRET });
59
+ expect(c.records[0].soul.decisionHistory.length).toBe(SOUL_EMBALMING_TUNABLES.DEFAULT_DECISION_HISTORY_LIMIT);
60
+ expect(c.records[0].soul.lastToolCalls.length).toBe(SOUL_EMBALMING_TUNABLES.DEFAULT_TOOL_CALL_LIMIT);
61
+ });
62
+ });
63
+ describe("v2.19.30 SOUL EMBALMING · restore (ban recovery)", () => {
64
+ it("restoreLatestSoul returns most recent + ban-recovery scenario verified", () => {
65
+ let c = emptyCrypt("agent-claude");
66
+ c = embalmSoul({ crypt: c, soul: mkSoul("agent-claude", 1000, "refactor auth.ts"), secret: SECRET });
67
+ c = embalmSoul({ crypt: c, soul: mkSoul("agent-claude", 5000, "refactor auth.ts → split into 3 files"), secret: SECRET });
68
+ const restored = restoreLatestSoul({ crypt: c, secret: SECRET });
69
+ expect(restored).not.toBeNull();
70
+ expect(restored.currentGoal).toContain("split into 3 files");
71
+ // The "new agent" (Codex/Gemini) receives this soul and continues without rollback.
72
+ });
73
+ it("restoreLatestSoul on empty crypt → null (defensive)", () => {
74
+ expect(restoreLatestSoul({ crypt: emptyCrypt("a"), secret: SECRET })).toBeNull();
75
+ });
76
+ it("restoreSoulAt supports negative index (-1 = newest)", () => {
77
+ let c = emptyCrypt("a");
78
+ c = embalmSoul({ crypt: c, soul: mkSoul("a", 1), secret: SECRET });
79
+ c = embalmSoul({ crypt: c, soul: mkSoul("a", 2), secret: SECRET });
80
+ expect(restoreSoulAt({ crypt: c, index: -1, secret: SECRET }).embalmedAtMs).toBe(2);
81
+ expect(restoreSoulAt({ crypt: c, index: 0, secret: SECRET }).embalmedAtMs).toBe(1);
82
+ });
83
+ it("out-of-range index → null", () => {
84
+ let c = emptyCrypt("a");
85
+ c = embalmSoul({ crypt: c, soul: mkSoul("a", 1), secret: SECRET });
86
+ expect(restoreSoulAt({ crypt: c, index: 99, secret: SECRET })).toBeNull();
87
+ });
88
+ it("tampered crypt → restoreLatestSoul returns null (fail-safe)", () => {
89
+ let c = emptyCrypt("a");
90
+ c = embalmSoul({ crypt: c, soul: mkSoul("a", 1), secret: SECRET });
91
+ const tampered = {
92
+ ...c,
93
+ records: c.records.map((r) => ({ ...r, soul: { ...r.soul, currentGoal: "INJECTED" } })),
94
+ };
95
+ expect(restoreLatestSoul({ crypt: tampered, secret: SECRET })).toBeNull();
96
+ });
97
+ });
98
+ describe("v2.19.30 SOUL EMBALMING · verifyCrypt", () => {
99
+ it("verifies untampered chain (10 souls)", () => {
100
+ let c = emptyCrypt("a");
101
+ for (let i = 0; i < 10; i++)
102
+ c = embalmSoul({ crypt: c, soul: mkSoul("a", i), secret: SECRET });
103
+ expect(verifyCrypt(c, SECRET)).toBe(true);
104
+ });
105
+ it("detects tamper at any step", () => {
106
+ let c = emptyCrypt("a");
107
+ for (let i = 0; i < 5; i++)
108
+ c = embalmSoul({ crypt: c, soul: mkSoul("a", i), secret: SECRET });
109
+ const tampered = {
110
+ ...c,
111
+ records: c.records.map((r, i) => i === 2 ? { ...r, soul: { ...r.soul, vendorAtEmbalm: "EVIL" } } : r),
112
+ };
113
+ expect(verifyCrypt(tampered, SECRET)).toBe(false);
114
+ });
115
+ it("MEASURED 100% determinism: same souls + secret → same sig (30 trials)", () => {
116
+ const make = () => {
117
+ let c = emptyCrypt("a");
118
+ c = embalmSoul({ crypt: c, soul: mkSoul("a", 1), secret: SECRET });
119
+ return c;
120
+ };
121
+ const first = make().records[0].sig;
122
+ let allEqual = true;
123
+ for (let i = 0; i < 30; i++) {
124
+ if (make().records[0].sig !== first) {
125
+ allEqual = false;
126
+ break;
127
+ }
128
+ }
129
+ expect(allEqual).toBe(true);
130
+ });
131
+ });
132
+ describe("v2.19.30 SOUL EMBALMING · stats + formatter", () => {
133
+ it("computeCryptStats reports totals + capacity + span", () => {
134
+ let c = emptyCrypt("a", 10);
135
+ c = embalmSoul({ crypt: c, soul: mkSoul("a", 0), secret: SECRET });
136
+ c = embalmSoul({ crypt: c, soul: mkSoul("a", 86400_000), secret: SECRET }); // 1 day later
137
+ const s = computeCryptStats(c);
138
+ expect(s.totalRecords).toBe(2);
139
+ expect(s.capacityUsed).toBeCloseTo(0.2, 5);
140
+ expect(s.spanMs).toBe(86400_000);
141
+ });
142
+ it("empty crypt stats → all zero / null (defensive)", () => {
143
+ const s = computeCryptStats(emptyCrypt("a"));
144
+ expect(s.totalRecords).toBe(0);
145
+ expect(s.oldestEmbalmedAtMs).toBeNull();
146
+ });
147
+ it("formatCryptLine renders one-line digest", () => {
148
+ const c = emptyCrypt("agent-claude");
149
+ expect(formatCryptLine(computeCryptStats(c))).toContain("CRYPT agent-claude");
150
+ });
151
+ });
152
+ describe("v2.19.30 SOUL EMBALMING · 24/7 resilience", () => {
153
+ it("MEASURED never crashes on 1000 random embalms + restores", () => {
154
+ let c = emptyCrypt("a", 100);
155
+ let crashed = false;
156
+ try {
157
+ for (let i = 0; i < 1000; i++) {
158
+ c = embalmSoul({ crypt: c, soul: mkSoul("a", i, `goal_${i}`), secret: SECRET });
159
+ restoreLatestSoul({ crypt: c, secret: SECRET });
160
+ restoreSoulAt({ crypt: c, index: i % 5, secret: SECRET });
161
+ }
162
+ }
163
+ catch {
164
+ crashed = true;
165
+ }
166
+ expect(crashed).toBe(false);
167
+ expect(verifyCrypt(c, SECRET)).toBe(true);
168
+ expect(c.records.length).toBe(100); // ring buffer enforced
169
+ });
170
+ });
171
+ //# sourceMappingURL=soul_embalming.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"soul_embalming.test.js","sourceRoot":"","sources":["../../src/soul_embalming/soul_embalming.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,142 @@
1
+ /**
2
+ * v2.19.31 — MNEME CROSS-DEVICE SYNAPSE SYNC (Phase D of SYNAPSE GENESIS)
3
+ *
4
+ * "บั๊กใหญ่มาก — ไม่สามารถ sync brain ข้าม device ได้. ต้องทำให้ใช้
5
+ * ได้ ผมถึงบอกว่าคุณต้องเทสเยอะๆ ว่ามัน sync brain ได้จริงๆ ข้าม mobile
6
+ * + computer + notebook"
7
+ * — user mandate, 2026-05-17
8
+ *
9
+ * Diagnosis: v2.19.29 SYNAPSE GENESIS learned weights locally. v2.19.30
10
+ * SOUL EMBALMING preserved them across BAN. Neither handles the third
11
+ * axis: the user works on mobile, laptop, and desktop. Each grows its
12
+ * own synapse store. Without a merge protocol, every device re-learns
13
+ * the same lessons from scratch and the brain never unifies.
14
+ *
15
+ * The protocol must be:
16
+ * - CRDT (commutative, associative, idempotent) so merge order
17
+ * doesn't matter (mobile→laptop ≡ laptop→mobile)
18
+ * - "Last-strongest-wins" per synapse key — the device that has
19
+ * observed a synapse most strongly / most recently provides the
20
+ * canonical weight, BUT permanent=true is sticky (once any device
21
+ * has crystallised a synapse, the merged result is permanent too)
22
+ * - Cumulative observationCount — total reinforcement across all
23
+ * devices, never lose evidence
24
+ * - HMAC-signed export envelopes — receivers verify before merging
25
+ * - Vendor-neutral — transport is caller-supplied (git branch via
26
+ * DIASPORA, HTTP bridge, USB stick, QR-code chain via BEACON,
27
+ * whatever the user prefers)
28
+ *
29
+ * Composes onto:
30
+ * - v2.19.29 Phase A HEBBIAN (SynapseWeight / SynapseStore types)
31
+ * - v2.19.30 SOUL EMBALMING (HMAC chain pattern)
32
+ * - v1.72 DIASPORA (transport — caller wires git/HTTP/QR)
33
+ *
34
+ * Honest scope:
35
+ * - PURE FUNCTION merge. Never throws.
36
+ * - HMAC-signed envelopes — forged exports auto-dropped.
37
+ * - Deterministic: same inputs → same merged store + same provenance map.
38
+ * - Defensive: empty exports, single-device, NaN weights, key collisions
39
+ * handled silently. 24/7 safe.
40
+ * - "permanent OR" semantics — never demotes a permanent synapse.
41
+ */
42
+ import type { SynapseStore } from "../synapse_genesis/index.js";
43
+ declare const PROTOCOL_VERSION: 1;
44
+ export interface DeviceSynapseExport {
45
+ v: typeof PROTOCOL_VERSION;
46
+ /** Stable, user-supplied device id (e.g. "macbook-pro-2026" or hash thereof). */
47
+ deviceId: string;
48
+ /** ms since epoch when the export was packaged. */
49
+ exportedAtMs: number;
50
+ store: SynapseStore;
51
+ /** HMAC over the canonical export body (everything above except sig). */
52
+ sig: string;
53
+ }
54
+ export interface MergeProvenance {
55
+ /** Composite synapse key. */
56
+ key: string;
57
+ /** deviceId whose weight + lastObservedAtMs won. */
58
+ winnerDeviceId: string;
59
+ /** All contributing devices (deviceId → contributing weight). */
60
+ contributors: Array<{
61
+ deviceId: string;
62
+ weight: number;
63
+ observationCount: number;
64
+ lastObservedAtMs: number;
65
+ permanent: boolean;
66
+ }>;
67
+ /** Final merged values. */
68
+ mergedWeight: number;
69
+ mergedObservationCount: number;
70
+ mergedPermanent: boolean;
71
+ mergedLastObservedAtMs: number;
72
+ }
73
+ export interface MergedSynapseResult {
74
+ v: typeof PROTOCOL_VERSION;
75
+ store: SynapseStore;
76
+ /** Per-key trace of which device contributed what — auditable. */
77
+ provenance: MergeProvenance[];
78
+ /** Devices that participated in the merge (after dedup). */
79
+ participatingDevices: string[];
80
+ /** Devices that were dropped because of bad signature / shape. */
81
+ rejectedDevices: string[];
82
+ }
83
+ /**
84
+ * Package a local store for cross-device transport.
85
+ * The envelope is HMAC-signed so receivers can detect tampering.
86
+ */
87
+ export declare function exportForSync(input: {
88
+ deviceId: string;
89
+ store: SynapseStore;
90
+ nowMs?: number;
91
+ secret?: string;
92
+ }): DeviceSynapseExport;
93
+ /** Verify an export envelope's HMAC. Returns false on forged / tampered envelopes. */
94
+ export declare function verifySyncExport(envelope: DeviceSynapseExport, secret?: string): boolean;
95
+ /**
96
+ * Merge N device exports into one canonical synapse store.
97
+ *
98
+ * Verifies each envelope's HMAC first; bad envelopes go into `rejectedDevices`
99
+ * and contribute nothing. Duplicate deviceIds: last-export-wins (most recent
100
+ * exportedAtMs).
101
+ *
102
+ * The merged store gets a freshly-recomputed signature with the local secret
103
+ * (so it can re-export). Caller (daemon) typically writes the merged store
104
+ * back to disk as the new local synapse_genesis state.
105
+ */
106
+ export declare function mergeSynapseStores(input: {
107
+ exports: DeviceSynapseExport[];
108
+ secret?: string;
109
+ /** Optional explicit synapse_genesis secret for the OUTPUT store sig. */
110
+ storeSecret?: string;
111
+ }): MergedSynapseResult;
112
+ /**
113
+ * DIASPORA-shape adapter: serialize an export envelope to a JSON path the
114
+ * caller's transport (git branch `diaspora/synapse-<deviceId>`, HTTP PUT,
115
+ * QR-chain, USB stick) can carry. Returns the canonical bytes + the
116
+ * recommended file path; the caller's chosen transport actually moves it.
117
+ */
118
+ export declare function packForDiaspora(envelope: DeviceSynapseExport): {
119
+ path: string;
120
+ bytes: string;
121
+ branchHint: string;
122
+ };
123
+ /**
124
+ * DIASPORA-shape unpack: read JSON bytes a caller fetched via their
125
+ * transport and return the typed envelope. Returns null on parse failure.
126
+ */
127
+ export declare function unpackFromDiaspora(bytes: string): DeviceSynapseExport | null;
128
+ export interface CrossDeviceSyncStats {
129
+ participatingDevices: number;
130
+ rejectedDevices: number;
131
+ totalSynapses: number;
132
+ permanentSynapses: number;
133
+ multiDeviceSynapses: number;
134
+ unifiedObservations: number;
135
+ }
136
+ export declare function computeSyncStats(result: MergedSynapseResult): CrossDeviceSyncStats;
137
+ export declare function formatSyncStatsLine(s: CrossDeviceSyncStats): string;
138
+ export declare const SYNAPSE_SYNC_TUNABLES: Readonly<{
139
+ PROTOCOL_VERSION: 1;
140
+ }>;
141
+ export {};
142
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/synapse_sync/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAiB,MAAM,6BAA6B,CAAC;AAE/E,QAAA,MAAM,gBAAgB,EAAG,CAAU,CAAC;AAEpC,MAAM,WAAW,mBAAmB;IAClC,CAAC,EAAE,OAAO,gBAAgB,CAAC;IAC3B,iFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,YAAY,CAAC;IACpB,yEAAyE;IACzE,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,eAAe;IAC9B,6BAA6B;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,oDAAoD;IACpD,cAAc,EAAE,MAAM,CAAC;IACvB,iEAAiE;IACjE,YAAY,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAClI,2BAA2B;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,eAAe,EAAE,OAAO,CAAC;IACzB,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,mBAAmB;IAClC,CAAC,EAAE,OAAO,gBAAgB,CAAC;IAC3B,KAAK,EAAE,YAAY,CAAC;IACpB,kEAAkE;IAClE,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,4DAA4D;IAC5D,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,kEAAkE;IAClE,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAsBD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,YAAY,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,mBAAmB,CAWtB;AAED,sFAAsF;AACtF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,mBAAmB,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CASxF;AA+ED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,mBAAmB,CAoFtB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,mBAAmB,GAAG;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB,CAaA;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI,CAM5E;AAED,MAAM,WAAW,oBAAoB;IACnC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,mBAAmB,GAAG,oBAAoB,CAiBlF;AAED,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,oBAAoB,GAAG,MAAM,CAEnE;AAED,eAAO,MAAM,qBAAqB;;EAEhC,CAAC"}
@@ -0,0 +1,323 @@
1
+ /**
2
+ * v2.19.31 — MNEME CROSS-DEVICE SYNAPSE SYNC (Phase D of SYNAPSE GENESIS)
3
+ *
4
+ * "บั๊กใหญ่มาก — ไม่สามารถ sync brain ข้าม device ได้. ต้องทำให้ใช้
5
+ * ได้ ผมถึงบอกว่าคุณต้องเทสเยอะๆ ว่ามัน sync brain ได้จริงๆ ข้าม mobile
6
+ * + computer + notebook"
7
+ * — user mandate, 2026-05-17
8
+ *
9
+ * Diagnosis: v2.19.29 SYNAPSE GENESIS learned weights locally. v2.19.30
10
+ * SOUL EMBALMING preserved them across BAN. Neither handles the third
11
+ * axis: the user works on mobile, laptop, and desktop. Each grows its
12
+ * own synapse store. Without a merge protocol, every device re-learns
13
+ * the same lessons from scratch and the brain never unifies.
14
+ *
15
+ * The protocol must be:
16
+ * - CRDT (commutative, associative, idempotent) so merge order
17
+ * doesn't matter (mobile→laptop ≡ laptop→mobile)
18
+ * - "Last-strongest-wins" per synapse key — the device that has
19
+ * observed a synapse most strongly / most recently provides the
20
+ * canonical weight, BUT permanent=true is sticky (once any device
21
+ * has crystallised a synapse, the merged result is permanent too)
22
+ * - Cumulative observationCount — total reinforcement across all
23
+ * devices, never lose evidence
24
+ * - HMAC-signed export envelopes — receivers verify before merging
25
+ * - Vendor-neutral — transport is caller-supplied (git branch via
26
+ * DIASPORA, HTTP bridge, USB stick, QR-code chain via BEACON,
27
+ * whatever the user prefers)
28
+ *
29
+ * Composes onto:
30
+ * - v2.19.29 Phase A HEBBIAN (SynapseWeight / SynapseStore types)
31
+ * - v2.19.30 SOUL EMBALMING (HMAC chain pattern)
32
+ * - v1.72 DIASPORA (transport — caller wires git/HTTP/QR)
33
+ *
34
+ * Honest scope:
35
+ * - PURE FUNCTION merge. Never throws.
36
+ * - HMAC-signed envelopes — forged exports auto-dropped.
37
+ * - Deterministic: same inputs → same merged store + same provenance map.
38
+ * - Defensive: empty exports, single-device, NaN weights, key collisions
39
+ * handled silently. 24/7 safe.
40
+ * - "permanent OR" semantics — never demotes a permanent synapse.
41
+ */
42
+ import { createHmac, timingSafeEqual } from "node:crypto";
43
+ const PROTOCOL_VERSION = 1;
44
+ function canon(v) {
45
+ if (v === null || typeof v !== "object")
46
+ return JSON.stringify(v);
47
+ if (Array.isArray(v))
48
+ return "[" + v.map(canon).join(",") + "]";
49
+ const keys = Object.keys(v).sort();
50
+ return "{" + keys.map((k) => JSON.stringify(k) + ":" + canon(v[k])).join(",") + "}";
51
+ }
52
+ function defaultSecret() {
53
+ return process.env["MNEME_SYNAPSE_SYNC_SECRET"] || `mneme-synapse-sync-v${PROTOCOL_VERSION}`;
54
+ }
55
+ function hmacHex(body, secret) {
56
+ return createHmac("sha256", secret).update(canon(body)).digest("hex");
57
+ }
58
+ function safeEqHex(a, b) {
59
+ try {
60
+ return timingSafeEqual(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
61
+ }
62
+ catch {
63
+ return false;
64
+ }
65
+ }
66
+ /**
67
+ * Package a local store for cross-device transport.
68
+ * The envelope is HMAC-signed so receivers can detect tampering.
69
+ */
70
+ export function exportForSync(input) {
71
+ const sec = input.secret ?? defaultSecret();
72
+ const exportedAtMs = input.nowMs ?? Date.now();
73
+ const body = {
74
+ v: PROTOCOL_VERSION,
75
+ deviceId: input.deviceId,
76
+ exportedAtMs,
77
+ store: input.store,
78
+ };
79
+ const sig = hmacHex(body, sec);
80
+ return { ...body, sig };
81
+ }
82
+ /** Verify an export envelope's HMAC. Returns false on forged / tampered envelopes. */
83
+ export function verifySyncExport(envelope, secret) {
84
+ if (!envelope || typeof envelope !== "object")
85
+ return false;
86
+ if (envelope.v !== PROTOCOL_VERSION)
87
+ return false;
88
+ if (typeof envelope.deviceId !== "string" || envelope.deviceId.length === 0)
89
+ return false;
90
+ if (typeof envelope.exportedAtMs !== "number" || !Number.isFinite(envelope.exportedAtMs))
91
+ return false;
92
+ if (!envelope.store || typeof envelope.store !== "object")
93
+ return false;
94
+ const sec = secret ?? defaultSecret();
95
+ const { sig, ...body } = envelope;
96
+ return safeEqHex(hmacHex(body, sec), sig);
97
+ }
98
+ /**
99
+ * CRDT merge rule applied per synapse-key across devices:
100
+ *
101
+ * weight: max( device.weight ) — "strongest wins"
102
+ * lastObservedAtMs: max( device.lastObservedAtMs ) — "most recent wins"
103
+ * observationCount: sum( device.observationCount ) — cumulative evidence
104
+ * permanent: OR( device.permanent ) — sticky / monotone
105
+ * permanentSinceWeight: min(>0) of device.permanentSinceWeight — first to crystallise
106
+ *
107
+ * Winner deviceId is the one whose (weight, lastObservedAtMs) pair lexicographically
108
+ * sorts highest — deterministic tie-break by deviceId ascending.
109
+ *
110
+ * Commutativity proof (sketch): max / sum / OR / min-over-positives are all
111
+ * commutative and associative; tie-break by deviceId is a total order, so
112
+ * mergeOrder doesn't matter.
113
+ */
114
+ function mergeWeightsAcrossDevices(input) {
115
+ const first = input[0];
116
+ const key = first.weight.key;
117
+ let mergedWeight = -Infinity;
118
+ let mergedLastMs = -Infinity;
119
+ let mergedObs = 0;
120
+ let mergedPermanent = false;
121
+ let mergedPermanentSinceWeight = 0;
122
+ // Winner tracking (deterministic tie-break)
123
+ let winnerDeviceId = first.deviceId;
124
+ let winnerWeight = -Infinity;
125
+ let winnerLastMs = -Infinity;
126
+ for (const { deviceId, weight: w } of input) {
127
+ if (Number.isFinite(w.weight) && w.weight > mergedWeight)
128
+ mergedWeight = w.weight;
129
+ if (Number.isFinite(w.lastObservedAtMs) && w.lastObservedAtMs > mergedLastMs)
130
+ mergedLastMs = w.lastObservedAtMs;
131
+ mergedObs += Math.max(0, w.observationCount | 0);
132
+ if (w.permanent)
133
+ mergedPermanent = true;
134
+ if (w.permanentSinceWeight > 0) {
135
+ mergedPermanentSinceWeight = mergedPermanentSinceWeight === 0
136
+ ? w.permanentSinceWeight
137
+ : Math.min(mergedPermanentSinceWeight, w.permanentSinceWeight);
138
+ }
139
+ // Winner: highest weight, tie-break by latest ts, then by deviceId asc.
140
+ const wWeight = Number.isFinite(w.weight) ? w.weight : -Infinity;
141
+ const wLast = Number.isFinite(w.lastObservedAtMs) ? w.lastObservedAtMs : -Infinity;
142
+ if (wWeight > winnerWeight ||
143
+ (wWeight === winnerWeight && wLast > winnerLastMs) ||
144
+ (wWeight === winnerWeight && wLast === winnerLastMs && deviceId < winnerDeviceId)) {
145
+ winnerDeviceId = deviceId;
146
+ winnerWeight = wWeight;
147
+ winnerLastMs = wLast;
148
+ }
149
+ }
150
+ // Clamp -Infinity guards (single-device empty case)
151
+ if (mergedWeight === -Infinity)
152
+ mergedWeight = 0;
153
+ if (mergedLastMs === -Infinity)
154
+ mergedLastMs = 0;
155
+ return {
156
+ key,
157
+ winnerDeviceId,
158
+ // Sort contributors by deviceId for deterministic output (commutativity).
159
+ contributors: input.map((x) => ({
160
+ deviceId: x.deviceId,
161
+ weight: x.weight.weight,
162
+ observationCount: x.weight.observationCount,
163
+ lastObservedAtMs: x.weight.lastObservedAtMs,
164
+ permanent: x.weight.permanent,
165
+ })).sort((a, b) => a.deviceId.localeCompare(b.deviceId)),
166
+ mergedWeight,
167
+ mergedObservationCount: mergedObs,
168
+ mergedPermanent,
169
+ mergedLastObservedAtMs: mergedLastMs,
170
+ };
171
+ }
172
+ /**
173
+ * Merge N device exports into one canonical synapse store.
174
+ *
175
+ * Verifies each envelope's HMAC first; bad envelopes go into `rejectedDevices`
176
+ * and contribute nothing. Duplicate deviceIds: last-export-wins (most recent
177
+ * exportedAtMs).
178
+ *
179
+ * The merged store gets a freshly-recomputed signature with the local secret
180
+ * (so it can re-export). Caller (daemon) typically writes the merged store
181
+ * back to disk as the new local synapse_genesis state.
182
+ */
183
+ export function mergeSynapseStores(input) {
184
+ const sec = input.secret ?? defaultSecret();
185
+ const storeSec = input.storeSecret
186
+ ?? process.env["MNEME_SYNAPSE_GENESIS_SECRET"]
187
+ ?? "mneme-synapse-genesis-v1";
188
+ const verifiedByDevice = new Map();
189
+ const rejected = [];
190
+ for (const env of input.exports ?? []) {
191
+ if (!verifySyncExport(env, sec)) {
192
+ if (env?.deviceId && typeof env.deviceId === "string")
193
+ rejected.push(env.deviceId);
194
+ continue;
195
+ }
196
+ const existing = verifiedByDevice.get(env.deviceId);
197
+ if (!existing || env.exportedAtMs > existing.exportedAtMs) {
198
+ verifiedByDevice.set(env.deviceId, env);
199
+ }
200
+ }
201
+ const participatingDevices = Array.from(verifiedByDevice.keys()).sort();
202
+ // Bucket weights by key
203
+ const byKey = new Map();
204
+ for (const [deviceId, env] of verifiedByDevice) {
205
+ for (const w of env.store?.weights ?? []) {
206
+ if (!w || typeof w.key !== "string")
207
+ continue;
208
+ const list = byKey.get(w.key) ?? [];
209
+ list.push({ deviceId, weight: w });
210
+ byKey.set(w.key, list);
211
+ }
212
+ }
213
+ const provenance = [];
214
+ const mergedWeights = [];
215
+ // Deterministic output order: sort keys lex
216
+ for (const key of Array.from(byKey.keys()).sort()) {
217
+ const bucket = byKey.get(key);
218
+ const prov = mergeWeightsAcrossDevices(bucket);
219
+ provenance.push(prov);
220
+ const exemplar = bucket[0].weight;
221
+ // permanentSinceWeight: min positive across contributors (first to crystallise)
222
+ let permSince = 0;
223
+ for (const c of bucket) {
224
+ if (c.weight.permanentSinceWeight > 0) {
225
+ permSince = permSince === 0 ? c.weight.permanentSinceWeight : Math.min(permSince, c.weight.permanentSinceWeight);
226
+ }
227
+ }
228
+ mergedWeights.push({
229
+ key,
230
+ eventPattern: exemplar.eventPattern,
231
+ toolName: exemplar.toolName,
232
+ weight: prov.mergedWeight,
233
+ observationCount: prov.mergedObservationCount,
234
+ lastObservedAtMs: prov.mergedLastObservedAtMs,
235
+ permanentSinceWeight: permSince,
236
+ permanent: prov.mergedPermanent,
237
+ });
238
+ }
239
+ // lastDecayedAtMs: max across participating stores
240
+ let lastDecayedAtMs = null;
241
+ for (const env of verifiedByDevice.values()) {
242
+ const t = env.store?.lastDecayedAtMs;
243
+ if (typeof t === "number" && Number.isFinite(t)) {
244
+ lastDecayedAtMs = lastDecayedAtMs === null ? t : Math.max(lastDecayedAtMs, t);
245
+ }
246
+ }
247
+ const baseStore = {
248
+ v: 1,
249
+ weights: mergedWeights,
250
+ lastDecayedAtMs,
251
+ };
252
+ const storeSig = createHmac("sha256", storeSec).update(canon(baseStore)).digest("hex");
253
+ const store = { ...baseStore, sig: storeSig };
254
+ return {
255
+ v: PROTOCOL_VERSION,
256
+ store,
257
+ provenance,
258
+ participatingDevices,
259
+ rejectedDevices: Array.from(new Set(rejected)).sort(),
260
+ };
261
+ }
262
+ /**
263
+ * DIASPORA-shape adapter: serialize an export envelope to a JSON path the
264
+ * caller's transport (git branch `diaspora/synapse-<deviceId>`, HTTP PUT,
265
+ * QR-chain, USB stick) can carry. Returns the canonical bytes + the
266
+ * recommended file path; the caller's chosen transport actually moves it.
267
+ */
268
+ export function packForDiaspora(envelope) {
269
+ // Sanitise: keep only [a-zA-Z0-9_-], then collapse runs of dots/slashes/etc.
270
+ // Two-stage scrub catches path traversal (..), shell metachars, and unicode.
271
+ const safeId = envelope.deviceId
272
+ .replace(/[^a-zA-Z0-9_-]/g, "_") // any non-alphanumeric → _
273
+ .replace(/_+/g, "_") // collapse runs
274
+ .replace(/^[_-]+|[_-]+$/g, "") // trim edges
275
+ .slice(0, 64) || "device";
276
+ return {
277
+ path: `.mneme/diaspora/synapse-${safeId}.json`,
278
+ bytes: JSON.stringify(envelope),
279
+ branchHint: `diaspora/synapse-${safeId}`,
280
+ };
281
+ }
282
+ /**
283
+ * DIASPORA-shape unpack: read JSON bytes a caller fetched via their
284
+ * transport and return the typed envelope. Returns null on parse failure.
285
+ */
286
+ export function unpackFromDiaspora(bytes) {
287
+ try {
288
+ const obj = JSON.parse(bytes);
289
+ if (obj && typeof obj === "object" && obj.v === PROTOCOL_VERSION)
290
+ return obj;
291
+ return null;
292
+ }
293
+ catch {
294
+ return null;
295
+ }
296
+ }
297
+ export function computeSyncStats(result) {
298
+ let permanent = 0;
299
+ let multiDevice = 0;
300
+ let observations = 0;
301
+ for (const p of result.provenance) {
302
+ if (p.mergedPermanent)
303
+ permanent++;
304
+ if (p.contributors.length > 1)
305
+ multiDevice++;
306
+ observations += p.mergedObservationCount;
307
+ }
308
+ return {
309
+ participatingDevices: result.participatingDevices.length,
310
+ rejectedDevices: result.rejectedDevices.length,
311
+ totalSynapses: result.store.weights.length,
312
+ permanentSynapses: permanent,
313
+ multiDeviceSynapses: multiDevice,
314
+ unifiedObservations: observations,
315
+ };
316
+ }
317
+ export function formatSyncStatsLine(s) {
318
+ return `🧬 SYNC ${s.participatingDevices}dev · ${s.totalSynapses} synapses · ${s.multiDeviceSynapses} multi-dev · ${s.permanentSynapses} perm · ${s.unifiedObservations} obs · ${s.rejectedDevices} rejected`;
319
+ }
320
+ export const SYNAPSE_SYNC_TUNABLES = Object.freeze({
321
+ PROTOCOL_VERSION,
322
+ });
323
+ //# sourceMappingURL=index.js.map