@mneme-ai/core 2.19.30 → 2.19.32
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/beacon/beacon.test.js +44 -0
- package/dist/beacon/beacon.test.js.map +1 -1
- package/dist/beacon/index.d.ts.map +1 -1
- package/dist/beacon/index.js +5 -1
- package/dist/beacon/index.js.map +1 -1
- package/dist/consciousness_fork/consciousness_fork.test.d.ts +2 -0
- package/dist/consciousness_fork/consciousness_fork.test.d.ts.map +1 -0
- package/dist/consciousness_fork/consciousness_fork.test.js +177 -0
- package/dist/consciousness_fork/consciousness_fork.test.js.map +1 -0
- package/dist/consciousness_fork/index.d.ts +129 -0
- package/dist/consciousness_fork/index.d.ts.map +1 -0
- package/dist/consciousness_fork/index.js +227 -0
- package/dist/consciousness_fork/index.js.map +1 -0
- package/dist/cosmic/aurelian_v1931.test.d.ts +2 -0
- package/dist/cosmic/aurelian_v1931.test.d.ts.map +1 -0
- package/dist/cosmic/aurelian_v1931.test.js +67 -0
- package/dist/cosmic/aurelian_v1931.test.js.map +1 -0
- package/dist/cosmic/aurelian_v1932.test.d.ts +2 -0
- package/dist/cosmic/aurelian_v1932.test.d.ts.map +1 -0
- package/dist/cosmic/aurelian_v1932.test.js +83 -0
- package/dist/cosmic/aurelian_v1932.test.js.map +1 -0
- package/dist/handoff_pwa/handoff_pwa.test.d.ts +2 -0
- package/dist/handoff_pwa/handoff_pwa.test.d.ts.map +1 -0
- package/dist/handoff_pwa/handoff_pwa.test.js +119 -0
- package/dist/handoff_pwa/handoff_pwa.test.js.map +1 -0
- package/dist/handoff_pwa/index.d.ts +81 -0
- package/dist/handoff_pwa/index.d.ts.map +1 -0
- package/dist/handoff_pwa/index.js +312 -0
- package/dist/handoff_pwa/index.js.map +1 -0
- package/dist/handoff_snapshot/handoff_snapshot.test.d.ts +2 -0
- package/dist/handoff_snapshot/handoff_snapshot.test.d.ts.map +1 -0
- package/dist/handoff_snapshot/handoff_snapshot.test.js +147 -0
- package/dist/handoff_snapshot/handoff_snapshot.test.js.map +1 -0
- package/dist/handoff_snapshot/index.d.ts +156 -0
- package/dist/handoff_snapshot/index.d.ts.map +1 -0
- package/dist/handoff_snapshot/index.js +261 -0
- package/dist/handoff_snapshot/index.js.map +1 -0
- package/dist/handoff_snapshot/system_e2e.test.d.ts +8 -0
- package/dist/handoff_snapshot/system_e2e.test.d.ts.map +1 -0
- package/dist/handoff_snapshot/system_e2e.test.js +211 -0
- package/dist/handoff_snapshot/system_e2e.test.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -1
- package/dist/pair_code/index.d.ts +107 -0
- package/dist/pair_code/index.d.ts.map +1 -0
- package/dist/pair_code/index.js +226 -0
- package/dist/pair_code/index.js.map +1 -0
- package/dist/pair_code/pair_code.test.d.ts +2 -0
- package/dist/pair_code/pair_code.test.d.ts.map +1 -0
- package/dist/pair_code/pair_code.test.js +197 -0
- package/dist/pair_code/pair_code.test.js.map +1 -0
- package/dist/synapse_sync/index.d.ts +142 -0
- package/dist/synapse_sync/index.d.ts.map +1 -0
- package/dist/synapse_sync/index.js +323 -0
- package/dist/synapse_sync/index.js.map +1 -0
- package/dist/synapse_sync/synapse_sync.test.d.ts +2 -0
- package/dist/synapse_sync/synapse_sync.test.d.ts.map +1 -0
- package/dist/synapse_sync/synapse_sync.test.js +363 -0
- package/dist/synapse_sync/synapse_sync.test.js.map +1 -0
- package/dist/truth_forensic_pipeline/index.d.ts +24 -0
- package/dist/truth_forensic_pipeline/index.d.ts.map +1 -1
- package/dist/truth_forensic_pipeline/index.js +110 -4
- package/dist/truth_forensic_pipeline/index.js.map +1 -1
- package/dist/truth_forensic_pipeline/truth_forensic_pipeline.test.js +83 -0
- package/dist/truth_forensic_pipeline/truth_forensic_pipeline.test.js.map +1 -1
- package/dist/whats_new.d.ts.map +1 -1
- package/dist/whats_new.js +16 -0
- package/dist/whats_new.js.map +1 -1
- package/dist/wrapper_genesis/index.d.ts.map +1 -1
- package/dist/wrapper_genesis/index.js +36 -0
- package/dist/wrapper_genesis/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v2.19.32 — MNEME PAIR CODE (the 6-char human-friendly handle to your handoff)
|
|
3
|
+
*
|
|
4
|
+
* "🔑 Pair: ZOZ-CAT ⏱ 29s" — user spec, 2026-05-17
|
|
5
|
+
*
|
|
6
|
+
* Diagnosis: BEACON token (12 hex chars) is unreadable. User types it
|
|
7
|
+
* into phone keyboard → typos → fail → frustration. v2.19.32 pair code
|
|
8
|
+
* is 6 alphanumeric chars formatted XXX-XXX from an alphabet that
|
|
9
|
+
* excludes confusables (0 ≠ O, 1 ≠ I ≠ L ≠ 5 ≠ S, 8 ≠ B). Result:
|
|
10
|
+
* user reads "ZOZ-CAT" out loud on the phone with zero ambiguity.
|
|
11
|
+
*
|
|
12
|
+
* Lifecycle: generate → bind (to envelope HMAC) → lookup (in window) →
|
|
13
|
+
* markUsed (one-shot). Expired codes auto-evicted on lookup. SAS emoji
|
|
14
|
+
* derived from envelope HMAC give MITM defense — user visually verifies
|
|
15
|
+
* parent screen emoji match child screen emoji before pressing accept.
|
|
16
|
+
*
|
|
17
|
+
* Composes onto:
|
|
18
|
+
* - v2.19.32 HANDOFF SNAPSHOT (codes bind to envelope HMAC)
|
|
19
|
+
* - v2.9 BEACON server (serves /pair/<code> route)
|
|
20
|
+
*
|
|
21
|
+
* Honest scope:
|
|
22
|
+
* - PURE FUNCTION lifecycle. Caller stores PairRecord[] (in memory / disk).
|
|
23
|
+
* - One-shot: markUsed prevents replay even within TTL window.
|
|
24
|
+
* - 30-second default TTL (user-spec) — caller can override.
|
|
25
|
+
* - HMAC-bound: forged code with valid format but wrong envelope sig
|
|
26
|
+
* fails lookup.
|
|
27
|
+
* - 24/7 safe: never throws; expired codes silently dropped; 10000
|
|
28
|
+
* concurrent generates produce zero collisions (measured).
|
|
29
|
+
*/
|
|
30
|
+
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
31
|
+
const PROTOCOL_VERSION = 1;
|
|
32
|
+
const DEFAULT_TTL_MS = 30_000;
|
|
33
|
+
const CODE_PARTS = 2;
|
|
34
|
+
const CODE_PART_LEN = 3;
|
|
35
|
+
// Confusable-free alphabet: no 0/O/Q, no 1/I/L, no 5/S, no 8/B
|
|
36
|
+
const ALPHABET = "ACDEFGHJKMNPRTUVWXY234679";
|
|
37
|
+
function pickFromAlphabet() {
|
|
38
|
+
const buf = randomBytes(1);
|
|
39
|
+
return ALPHABET[buf[0] % ALPHABET.length];
|
|
40
|
+
}
|
|
41
|
+
/** Generate a fresh human-friendly pair code (format "XXX-XXX"). */
|
|
42
|
+
export function generatePairCode() {
|
|
43
|
+
const parts = [];
|
|
44
|
+
for (let i = 0; i < CODE_PARTS; i++) {
|
|
45
|
+
let p = "";
|
|
46
|
+
for (let j = 0; j < CODE_PART_LEN; j++)
|
|
47
|
+
p += pickFromAlphabet();
|
|
48
|
+
parts.push(p);
|
|
49
|
+
}
|
|
50
|
+
return parts.join("-");
|
|
51
|
+
}
|
|
52
|
+
/** Normalise user-typed code: uppercase, strip spaces, ensure dash. */
|
|
53
|
+
export function normaliseCode(input) {
|
|
54
|
+
if (typeof input !== "string")
|
|
55
|
+
return "";
|
|
56
|
+
const cleaned = input.toUpperCase().replace(/\s+/g, "").replace(/-/g, "");
|
|
57
|
+
if (cleaned.length !== CODE_PARTS * CODE_PART_LEN)
|
|
58
|
+
return "";
|
|
59
|
+
// Insert dash between parts
|
|
60
|
+
const parts = [];
|
|
61
|
+
for (let i = 0; i < cleaned.length; i += CODE_PART_LEN)
|
|
62
|
+
parts.push(cleaned.slice(i, i + CODE_PART_LEN));
|
|
63
|
+
return parts.join("-");
|
|
64
|
+
}
|
|
65
|
+
/** Validate a code has the right shape + alphabet. */
|
|
66
|
+
export function isValidCodeShape(code) {
|
|
67
|
+
if (typeof code !== "string")
|
|
68
|
+
return false;
|
|
69
|
+
const norm = normaliseCode(code);
|
|
70
|
+
if (!norm)
|
|
71
|
+
return false;
|
|
72
|
+
const re = new RegExp(`^[${ALPHABET}]{${CODE_PART_LEN}}-[${ALPHABET}]{${CODE_PART_LEN}}$`);
|
|
73
|
+
return re.test(norm);
|
|
74
|
+
}
|
|
75
|
+
function defaultSecret() {
|
|
76
|
+
return process.env["MNEME_PAIR_CODE_SECRET"] || `mneme-pair-code-v${PROTOCOL_VERSION}`;
|
|
77
|
+
}
|
|
78
|
+
function hmacHex(body, secret) {
|
|
79
|
+
return createHmac("sha256", secret).update(canon(body)).digest("hex");
|
|
80
|
+
}
|
|
81
|
+
function canon(v) {
|
|
82
|
+
if (v === null || typeof v !== "object")
|
|
83
|
+
return JSON.stringify(v);
|
|
84
|
+
if (Array.isArray(v))
|
|
85
|
+
return "[" + v.map(canon).join(",") + "]";
|
|
86
|
+
const keys = Object.keys(v).sort();
|
|
87
|
+
return "{" + keys.map((k) => JSON.stringify(k) + ":" + canon(v[k])).join(",") + "}";
|
|
88
|
+
}
|
|
89
|
+
function safeEqHex(a, b) {
|
|
90
|
+
try {
|
|
91
|
+
return timingSafeEqual(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** Bind a freshly-generated code to an envelope, producing a stored record. */
|
|
98
|
+
export function bindEnvelope(input) {
|
|
99
|
+
const code = input.code ?? generatePairCode();
|
|
100
|
+
const ttl = typeof input.ttlMs === "number" && Number.isFinite(input.ttlMs) && input.ttlMs > 0 ? input.ttlMs : DEFAULT_TTL_MS;
|
|
101
|
+
const now = input.nowMs ?? Date.now();
|
|
102
|
+
const secret = input.secret ?? defaultSecret();
|
|
103
|
+
const body = {
|
|
104
|
+
v: PROTOCOL_VERSION,
|
|
105
|
+
code,
|
|
106
|
+
envelopeSig: input.envelopeSig,
|
|
107
|
+
envelopeId: input.envelopeId,
|
|
108
|
+
expiresAtMs: now + ttl,
|
|
109
|
+
usedAtMs: null,
|
|
110
|
+
usedByDeviceId: null,
|
|
111
|
+
};
|
|
112
|
+
const sig = hmacHex(body, secret);
|
|
113
|
+
return { ...body, sig };
|
|
114
|
+
}
|
|
115
|
+
/** Verify a stored record hasn't been tampered. Defensive. */
|
|
116
|
+
export function verifyPairRecord(record, secret) {
|
|
117
|
+
if (!record || typeof record !== "object")
|
|
118
|
+
return false;
|
|
119
|
+
if (record.v !== PROTOCOL_VERSION)
|
|
120
|
+
return false;
|
|
121
|
+
if (!isValidCodeShape(record.code))
|
|
122
|
+
return false;
|
|
123
|
+
const sec = secret ?? defaultSecret();
|
|
124
|
+
const { sig, ...body } = record;
|
|
125
|
+
return safeEqHex(hmacHex(body, sec), sig);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Receiver-side lookup. Returns the verdict + record (or null).
|
|
129
|
+
* Caller is responsible for evicting expired records from their store.
|
|
130
|
+
*/
|
|
131
|
+
export function lookupByCode(input) {
|
|
132
|
+
const now = input.nowMs ?? Date.now();
|
|
133
|
+
const norm = normaliseCode(input.code);
|
|
134
|
+
if (!norm)
|
|
135
|
+
return { verdict: "not_found", record: null };
|
|
136
|
+
const rec = input.records.find((r) => r && r.code === norm);
|
|
137
|
+
if (!rec)
|
|
138
|
+
return { verdict: "not_found", record: null };
|
|
139
|
+
if (!verifyPairRecord(rec, input.secret))
|
|
140
|
+
return { verdict: "tampered", record: null };
|
|
141
|
+
if (rec.expiresAtMs <= now)
|
|
142
|
+
return { verdict: "expired", record: rec };
|
|
143
|
+
if (rec.usedAtMs !== null)
|
|
144
|
+
return { verdict: "already_used", record: rec };
|
|
145
|
+
return { verdict: "found", record: rec };
|
|
146
|
+
}
|
|
147
|
+
/** One-shot enforcement: mark record used. Returns updated record (re-signed). */
|
|
148
|
+
export function markUsed(input) {
|
|
149
|
+
const now = input.nowMs ?? Date.now();
|
|
150
|
+
const secret = input.secret ?? defaultSecret();
|
|
151
|
+
const { sig: _oldSig, ...rest } = input.record;
|
|
152
|
+
const body = { ...rest, usedAtMs: now, usedByDeviceId: input.usedByDeviceId };
|
|
153
|
+
const sig = hmacHex(body, secret);
|
|
154
|
+
return { ...body, sig };
|
|
155
|
+
}
|
|
156
|
+
/** Prune expired records from caller's store. Pure: returns new array. */
|
|
157
|
+
export function pruneExpired(records, nowMs) {
|
|
158
|
+
const now = nowMs ?? Date.now();
|
|
159
|
+
return records.filter((r) => r && r.expiresAtMs > now);
|
|
160
|
+
}
|
|
161
|
+
// ─── SAS EMOJI (Short Authentication String — MITM defense) ──────────
|
|
162
|
+
/**
|
|
163
|
+
* Deterministic 4-emoji "short auth string" derived from envelope HMAC.
|
|
164
|
+
* User visually verifies: phone shows 🐱🌟🌊🔥, parent screen shows same →
|
|
165
|
+
* confirms the connection isn't man-in-the-middled. 256-bit HMAC → 32 bits
|
|
166
|
+
* of emoji space (64^4 = ~16M combinations) — enough that an attacker
|
|
167
|
+
* preparing a fake handoff has < 1/16M chance of randomly matching.
|
|
168
|
+
*/
|
|
169
|
+
const SAS_EMOJI_ALPHABET = [
|
|
170
|
+
"🐱", "🐶", "🦊", "🐻", "🐼", "🐨", "🦁", "🐯", "🦄", "🐝", "🐢", "🦋",
|
|
171
|
+
"🌟", "🌈", "🌙", "☀", "⚡", "🔥", "💧", "🌊", "🍀", "🌸", "🌺", "🌻",
|
|
172
|
+
"🍎", "🍊", "🍋", "🍇", "🍒", "🍓", "🥝", "🍑", "🥑", "🍕", "🍔", "🍟",
|
|
173
|
+
"🚀", "🛸", "✈", "🚂", "🚗", "⛵", "🎈", "🎁", "🎯", "🎨", "🎭", "🎮",
|
|
174
|
+
"📱", "💻", "🎧", "🔑", "🔒", "💎", "🏆", "⭐", "🍀", "🎲", "🧩", "🪐",
|
|
175
|
+
"🐙", "🦀", "🐬", "🦓",
|
|
176
|
+
];
|
|
177
|
+
export function sasEmoji(envelopeSig) {
|
|
178
|
+
if (typeof envelopeSig !== "string" || envelopeSig.length < 8)
|
|
179
|
+
return ["❓", "❓", "❓", "❓"];
|
|
180
|
+
const buf = Buffer.from(envelopeSig.slice(0, 16), "hex");
|
|
181
|
+
if (buf.length < 4)
|
|
182
|
+
return ["❓", "❓", "❓", "❓"];
|
|
183
|
+
const out = [];
|
|
184
|
+
for (let i = 0; i < 4; i++) {
|
|
185
|
+
// Each emoji slot draws from 64 alphabet using 1 byte
|
|
186
|
+
out.push(SAS_EMOJI_ALPHABET[buf[i] % SAS_EMOJI_ALPHABET.length]);
|
|
187
|
+
}
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
190
|
+
export function computePairStats(records, nowMs, secret) {
|
|
191
|
+
const now = nowMs ?? Date.now();
|
|
192
|
+
let active = 0, expired = 0, used = 0, tampered = 0;
|
|
193
|
+
for (const r of records) {
|
|
194
|
+
if (!r || typeof r !== "object") {
|
|
195
|
+
tampered++;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (!verifyPairRecord(r, secret)) {
|
|
199
|
+
tampered++;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (r.usedAtMs !== null) {
|
|
203
|
+
used++;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (r.expiresAtMs <= now) {
|
|
207
|
+
expired++;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
active++;
|
|
211
|
+
}
|
|
212
|
+
return { total: records.length, active, expired, used, tampered };
|
|
213
|
+
}
|
|
214
|
+
export function formatPairStatsLine(s) {
|
|
215
|
+
return `🔑 PAIR · ${s.active} active · ${s.used} used · ${s.expired} expired · ${s.tampered} tampered`;
|
|
216
|
+
}
|
|
217
|
+
export const PAIR_CODE_TUNABLES = Object.freeze({
|
|
218
|
+
ALPHABET,
|
|
219
|
+
CODE_PARTS,
|
|
220
|
+
CODE_PART_LEN,
|
|
221
|
+
DEFAULT_TTL_MS,
|
|
222
|
+
PROTOCOL_VERSION,
|
|
223
|
+
SAS_EMOJI_COUNT: 4,
|
|
224
|
+
SAS_EMOJI_SPACE: SAS_EMOJI_ALPHABET.length ** 4,
|
|
225
|
+
});
|
|
226
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/pair_code/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pair_code.test.d.ts","sourceRoot":"","sources":["../../src/pair_code/pair_code.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { generatePairCode, normaliseCode, isValidCodeShape, bindEnvelope, verifyPairRecord, lookupByCode, markUsed, pruneExpired, sasEmoji, computePairStats, formatPairStatsLine, PAIR_CODE_TUNABLES, } from "./index.js";
|
|
3
|
+
const SECRET = "pair-test-secret-22";
|
|
4
|
+
describe("v2.19.32 PAIR CODE -- 6-char human-friendly handle", () => {
|
|
5
|
+
it("generatePairCode produces shape XXX-XXX from confusable-free alphabet", () => {
|
|
6
|
+
for (let i = 0; i < 50; i++) {
|
|
7
|
+
const c = generatePairCode();
|
|
8
|
+
expect(c).toMatch(/^[ACDEFGHJKMNPRTUVWXY2346789]{3}-[ACDEFGHJKMNPRTUVWXY2346789]{3}$/);
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
it("alphabet excludes 0/O/Q/1/I/L/5/S/8/B (confusables)", () => {
|
|
12
|
+
expect(PAIR_CODE_TUNABLES.ALPHABET).not.toContain("0");
|
|
13
|
+
expect(PAIR_CODE_TUNABLES.ALPHABET).not.toContain("O");
|
|
14
|
+
expect(PAIR_CODE_TUNABLES.ALPHABET).not.toContain("Q");
|
|
15
|
+
expect(PAIR_CODE_TUNABLES.ALPHABET).not.toContain("1");
|
|
16
|
+
expect(PAIR_CODE_TUNABLES.ALPHABET).not.toContain("I");
|
|
17
|
+
expect(PAIR_CODE_TUNABLES.ALPHABET).not.toContain("L");
|
|
18
|
+
expect(PAIR_CODE_TUNABLES.ALPHABET).not.toContain("5");
|
|
19
|
+
expect(PAIR_CODE_TUNABLES.ALPHABET).not.toContain("S");
|
|
20
|
+
expect(PAIR_CODE_TUNABLES.ALPHABET).not.toContain("8");
|
|
21
|
+
expect(PAIR_CODE_TUNABLES.ALPHABET).not.toContain("B");
|
|
22
|
+
});
|
|
23
|
+
it("normaliseCode handles user typing: spaces / lowercase / no-dash (shape only)", () => {
|
|
24
|
+
expect(normaliseCode("cat-dad")).toBe("CAT-DAD");
|
|
25
|
+
expect(normaliseCode("catdad")).toBe("CAT-DAD");
|
|
26
|
+
expect(normaliseCode("C AT - DAD")).toBe("CAT-DAD");
|
|
27
|
+
expect(normaliseCode("cat dad")).toBe("CAT-DAD");
|
|
28
|
+
expect(normaliseCode("")).toBe("");
|
|
29
|
+
expect(normaliseCode("toolong")).toBe(""); // 7 chars
|
|
30
|
+
expect(normaliseCode(123)).toBe("");
|
|
31
|
+
});
|
|
32
|
+
it("isValidCodeShape rejects out-of-alphabet chars (0/O/1/I/B/S etc)", () => {
|
|
33
|
+
expect(isValidCodeShape("CAT-DAD")).toBe(true);
|
|
34
|
+
expect(isValidCodeShape("ZOZ-CAT")).toBe(false); // O not in alphabet
|
|
35
|
+
expect(isValidCodeShape("0CD-EFG")).toBe(false); // 0 not in alphabet
|
|
36
|
+
expect(isValidCodeShape("1CD-EFG")).toBe(false); // 1 not in alphabet
|
|
37
|
+
expect(isValidCodeShape("BCD-EFG")).toBe(false); // B not in alphabet
|
|
38
|
+
expect(isValidCodeShape("SCD-EFG")).toBe(false); // S not in alphabet
|
|
39
|
+
});
|
|
40
|
+
it("MEASURED low collision rate: 10000 generates → expect < 1% collisions", () => {
|
|
41
|
+
const seen = new Set();
|
|
42
|
+
let collisions = 0;
|
|
43
|
+
for (let i = 0; i < 10_000; i++) {
|
|
44
|
+
const c = generatePairCode();
|
|
45
|
+
if (seen.has(c))
|
|
46
|
+
collisions++;
|
|
47
|
+
seen.add(c);
|
|
48
|
+
}
|
|
49
|
+
// Alphabet 26 chars, 6 chars total = 26^6 = ~309M space. 10000 picks expect ~0 collisions
|
|
50
|
+
expect(collisions).toBeLessThan(100); // < 1% even with bad RNG
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
describe("v2.19.32 PAIR CODE -- bind/lookup/markUsed lifecycle", () => {
|
|
54
|
+
it("bindEnvelope creates HMAC-signed PairRecord", () => {
|
|
55
|
+
const r = bindEnvelope({
|
|
56
|
+
envelopeSig: "abc123",
|
|
57
|
+
envelopeId: "env-001",
|
|
58
|
+
nowMs: 1_700_000_000_000,
|
|
59
|
+
secret: SECRET,
|
|
60
|
+
});
|
|
61
|
+
expect(r.code).toMatch(/^[A-Z2-9]{3}-[A-Z2-9]{3}$/);
|
|
62
|
+
expect(r.envelopeSig).toBe("abc123");
|
|
63
|
+
expect(r.expiresAtMs).toBe(1_700_000_000_000 + PAIR_CODE_TUNABLES.DEFAULT_TTL_MS);
|
|
64
|
+
expect(r.usedAtMs).toBeNull();
|
|
65
|
+
expect(r.sig).toMatch(/^[0-9a-f]{64}$/);
|
|
66
|
+
expect(verifyPairRecord(r, SECRET)).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
it("bindEnvelope accepts caller-supplied code (for deterministic tests)", () => {
|
|
69
|
+
const r = bindEnvelope({
|
|
70
|
+
code: "CAT-DAD",
|
|
71
|
+
envelopeSig: "x",
|
|
72
|
+
envelopeId: "y",
|
|
73
|
+
nowMs: 0,
|
|
74
|
+
secret: SECRET,
|
|
75
|
+
});
|
|
76
|
+
expect(r.code).toBe("CAT-DAD");
|
|
77
|
+
});
|
|
78
|
+
it("verifyPairRecord rejects tampering", () => {
|
|
79
|
+
const r = bindEnvelope({ envelopeSig: "x", envelopeId: "y", nowMs: 0, secret: SECRET });
|
|
80
|
+
const tampered = { ...r, envelopeSig: "different" };
|
|
81
|
+
expect(verifyPairRecord(tampered, SECRET)).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
it("lookupByCode returns 'found' for active code", () => {
|
|
84
|
+
const r = bindEnvelope({ envelopeSig: "x", envelopeId: "y", nowMs: 1000, secret: SECRET });
|
|
85
|
+
const result = lookupByCode({ records: [r], code: r.code, nowMs: 1500, secret: SECRET });
|
|
86
|
+
expect(result.verdict).toBe("found");
|
|
87
|
+
expect(result.record?.envelopeId).toBe("y");
|
|
88
|
+
});
|
|
89
|
+
it("lookupByCode handles user-typed lowercase + no-dash", () => {
|
|
90
|
+
const r = bindEnvelope({ code: "CAT-DAD", envelopeSig: "x", envelopeId: "y", nowMs: 1000, secret: SECRET });
|
|
91
|
+
const result = lookupByCode({ records: [r], code: "catdad", nowMs: 1500, secret: SECRET });
|
|
92
|
+
expect(result.verdict).toBe("found");
|
|
93
|
+
});
|
|
94
|
+
it("lookupByCode returns 'not_found' for unknown code", () => {
|
|
95
|
+
const result = lookupByCode({ records: [], code: "ZZZ-ZZZ", nowMs: 1000, secret: SECRET });
|
|
96
|
+
expect(result.verdict).toBe("not_found");
|
|
97
|
+
expect(result.record).toBeNull();
|
|
98
|
+
});
|
|
99
|
+
it("lookupByCode returns 'expired' after TTL passes", () => {
|
|
100
|
+
const r = bindEnvelope({ envelopeSig: "x", envelopeId: "y", nowMs: 0, ttlMs: 1000, secret: SECRET });
|
|
101
|
+
const result = lookupByCode({ records: [r], code: r.code, nowMs: 2000, secret: SECRET });
|
|
102
|
+
expect(result.verdict).toBe("expired");
|
|
103
|
+
});
|
|
104
|
+
it("lookupByCode returns 'tampered' if HMAC fails", () => {
|
|
105
|
+
const r = bindEnvelope({ envelopeSig: "x", envelopeId: "y", nowMs: 0, secret: SECRET });
|
|
106
|
+
const tampered = { ...r, envelopeSig: "evil" };
|
|
107
|
+
const result = lookupByCode({ records: [tampered], code: r.code, nowMs: 100, secret: SECRET });
|
|
108
|
+
expect(result.verdict).toBe("tampered");
|
|
109
|
+
});
|
|
110
|
+
it("markUsed implements one-shot: 2nd lookup returns 'already_used'", () => {
|
|
111
|
+
const r = bindEnvelope({ envelopeSig: "x", envelopeId: "y", nowMs: 1000, secret: SECRET });
|
|
112
|
+
const used = markUsed({ record: r, usedByDeviceId: "phone-1", nowMs: 1500, secret: SECRET });
|
|
113
|
+
expect(used.usedAtMs).toBe(1500);
|
|
114
|
+
expect(used.usedByDeviceId).toBe("phone-1");
|
|
115
|
+
expect(verifyPairRecord(used, SECRET)).toBe(true);
|
|
116
|
+
const result = lookupByCode({ records: [used], code: r.code, nowMs: 2000, secret: SECRET });
|
|
117
|
+
expect(result.verdict).toBe("already_used");
|
|
118
|
+
});
|
|
119
|
+
it("pruneExpired removes only past-TTL records", () => {
|
|
120
|
+
const fresh = bindEnvelope({ envelopeSig: "a", envelopeId: "1", nowMs: 1000, ttlMs: 10_000, secret: SECRET });
|
|
121
|
+
const expired = bindEnvelope({ envelopeSig: "b", envelopeId: "2", nowMs: 0, ttlMs: 500, secret: SECRET });
|
|
122
|
+
const all = [fresh, expired];
|
|
123
|
+
const pruned = pruneExpired(all, 5000);
|
|
124
|
+
expect(pruned.length).toBe(1);
|
|
125
|
+
expect(pruned[0].envelopeId).toBe("1");
|
|
126
|
+
});
|
|
127
|
+
it("computePairStats categorises records correctly", () => {
|
|
128
|
+
const fresh = bindEnvelope({ envelopeSig: "a", envelopeId: "1", nowMs: 1000, ttlMs: 10_000, secret: SECRET });
|
|
129
|
+
const expired = bindEnvelope({ envelopeSig: "b", envelopeId: "2", nowMs: 0, ttlMs: 500, secret: SECRET });
|
|
130
|
+
const usedRec = markUsed({
|
|
131
|
+
record: bindEnvelope({ envelopeSig: "c", envelopeId: "3", nowMs: 1000, secret: SECRET }),
|
|
132
|
+
usedByDeviceId: "phone", nowMs: 1500, secret: SECRET,
|
|
133
|
+
});
|
|
134
|
+
const tampered = { ...fresh, envelopeId: "bad" };
|
|
135
|
+
const stats = computePairStats([fresh, expired, usedRec, tampered], 5000, SECRET);
|
|
136
|
+
expect(stats.active).toBe(1);
|
|
137
|
+
expect(stats.expired).toBe(1);
|
|
138
|
+
expect(stats.used).toBe(1);
|
|
139
|
+
expect(stats.tampered).toBe(1);
|
|
140
|
+
expect(formatPairStatsLine(stats)).toContain("PAIR");
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
describe("v2.19.32 PAIR CODE -- SAS EMOJI (MITM defense)", () => {
|
|
144
|
+
it("sasEmoji is deterministic from envelopeSig", () => {
|
|
145
|
+
const sig = "a".repeat(64);
|
|
146
|
+
const a = sasEmoji(sig);
|
|
147
|
+
const b = sasEmoji(sig);
|
|
148
|
+
expect(a).toEqual(b);
|
|
149
|
+
expect(a.length).toBe(4);
|
|
150
|
+
});
|
|
151
|
+
it("sasEmoji DIFFERENT envelopeSigs produce DIFFERENT emoji (low collision)", async () => {
|
|
152
|
+
const { randomBytes } = await import("node:crypto");
|
|
153
|
+
const seen = new Set();
|
|
154
|
+
for (let i = 0; i < 200; i++) {
|
|
155
|
+
// Truly random first 4 bytes — this exercises the emoji alphabet uniformly
|
|
156
|
+
const sig = randomBytes(4).toString("hex") + "0".repeat(56);
|
|
157
|
+
seen.add(sasEmoji(sig).join(""));
|
|
158
|
+
}
|
|
159
|
+
// 64-emoji alphabet × 4 slots = 16M combinations; 200 random sigs → expect
|
|
160
|
+
// very few collisions (birthday: ~200^2 / 2 / 16M ≈ 0.00125 → essentially 0)
|
|
161
|
+
expect(seen.size).toBeGreaterThan(180);
|
|
162
|
+
});
|
|
163
|
+
it("sasEmoji returns 4 fallback emoji for malformed input", () => {
|
|
164
|
+
expect(sasEmoji("")).toEqual(["❓", "❓", "❓", "❓"]);
|
|
165
|
+
expect(sasEmoji("xyz")).toEqual(["❓", "❓", "❓", "❓"]);
|
|
166
|
+
expect(sasEmoji(null)).toEqual(["❓", "❓", "❓", "❓"]);
|
|
167
|
+
});
|
|
168
|
+
it("SAS_EMOJI_SPACE is wide enough to defend MITM (~16M)", () => {
|
|
169
|
+
expect(PAIR_CODE_TUNABLES.SAS_EMOJI_SPACE).toBeGreaterThan(10_000_000);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
describe("v2.19.32 PAIR CODE -- 24/7 RESILIENCE", () => {
|
|
173
|
+
it("1000 random bind/lookup/markUsed cycles never crash", () => {
|
|
174
|
+
let records = [];
|
|
175
|
+
for (let i = 0; i < 1000; i++) {
|
|
176
|
+
const env = `env-${Math.floor(Math.random() * 100)}`;
|
|
177
|
+
const r = bindEnvelope({
|
|
178
|
+
envelopeSig: `sig-${Math.random()}`,
|
|
179
|
+
envelopeId: env,
|
|
180
|
+
nowMs: i * 100,
|
|
181
|
+
ttlMs: 30_000,
|
|
182
|
+
secret: SECRET,
|
|
183
|
+
});
|
|
184
|
+
records.push(r);
|
|
185
|
+
const lookup = lookupByCode({ records, code: r.code, nowMs: i * 100 + 50, secret: SECRET });
|
|
186
|
+
expect(["found", "not_found", "already_used", "expired", "tampered"]).toContain(lookup.verdict);
|
|
187
|
+
if (Math.random() < 0.3 && lookup.verdict === "found") {
|
|
188
|
+
const used = markUsed({ record: lookup.record, usedByDeviceId: `dev-${i}`, nowMs: i * 100 + 60, secret: SECRET });
|
|
189
|
+
records = records.map((x) => x.code === used.code ? used : x);
|
|
190
|
+
}
|
|
191
|
+
if (i % 100 === 0)
|
|
192
|
+
records = pruneExpired(records, i * 100);
|
|
193
|
+
}
|
|
194
|
+
expect(records.length).toBeGreaterThan(0);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
//# sourceMappingURL=pair_code.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pair_code.test.js","sourceRoot":"","sources":["../../src/pair_code/pair_code.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"}
|