yakmesh 1.1.0 → 1.3.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/CHANGELOG.md +65 -75
- package/assets/yakmesh-logo2sm.png +0 -0
- package/assets/ymsm.png +0 -0
- package/discord.md +74 -0
- package/mesh/beacon-broadcast.js +655 -0
- package/mesh/echo-ranging.js +612 -0
- package/mesh/phantom-routing.js +700 -0
- package/mesh/pulse-sync.js +618 -0
- package/mesh/temporal-encoder.js +383 -0
- package/package.json +82 -51
- package/test-novel-systems.mjs +398 -0
- package/test-tme.mjs +383 -0
package/test-tme.mjs
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* YAKMESH™ Temporal Mesh Encoding (TME) Test Suite
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import assert from 'assert';
|
|
6
|
+
import {
|
|
7
|
+
TME_CONFIG,
|
|
8
|
+
TemporalSlice,
|
|
9
|
+
TemporalStream,
|
|
10
|
+
TemporalReconstructor,
|
|
11
|
+
TemporalMeshEncoder,
|
|
12
|
+
} from './mesh/temporal-encoder.js';
|
|
13
|
+
|
|
14
|
+
// Test utilities
|
|
15
|
+
let passed = 0;
|
|
16
|
+
let failed = 0;
|
|
17
|
+
|
|
18
|
+
function test(name, fn) {
|
|
19
|
+
try {
|
|
20
|
+
fn();
|
|
21
|
+
console.log('✅ ' + name);
|
|
22
|
+
passed++;
|
|
23
|
+
} catch (err) {
|
|
24
|
+
console.log('❌ ' + name + ': ' + err.message);
|
|
25
|
+
failed++;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function section(name) {
|
|
30
|
+
console.log('\n─── ' + name + ' ───\n');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log('╔══════════════════════════════════════════════════════╗');
|
|
34
|
+
console.log('║ TME (TEMPORAL MESH ENCODING) TEST SUITE ║');
|
|
35
|
+
console.log('╚══════════════════════════════════════════════════════╝');
|
|
36
|
+
|
|
37
|
+
// =============================================================================
|
|
38
|
+
section('TemporalSlice Tests');
|
|
39
|
+
// =============================================================================
|
|
40
|
+
|
|
41
|
+
test('TemporalSlice creates with valid temporal hash', () => {
|
|
42
|
+
const slice = new TemporalSlice({
|
|
43
|
+
data: Buffer.from('Hello TME'),
|
|
44
|
+
timestamp: BigInt(Date.now() * 1_000_000),
|
|
45
|
+
sequenceNumber: 0,
|
|
46
|
+
streamId: 'test-stream-123',
|
|
47
|
+
meshPosition: [1.0, 2.0, 3.0],
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
assert(slice.temporalHash.length === 32, 'Temporal hash should be 32 bytes');
|
|
51
|
+
assert(slice.verify(), 'Slice should verify');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('TemporalSlice serializes and deserializes correctly', () => {
|
|
55
|
+
const original = new TemporalSlice({
|
|
56
|
+
data: Buffer.from('Test data for serialization'),
|
|
57
|
+
timestamp: BigInt(1234567890000000000n),
|
|
58
|
+
sequenceNumber: 5,
|
|
59
|
+
streamId: 'serialize-test',
|
|
60
|
+
meshPosition: [10.5, 20.5, 30.5],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const serialized = original.serialize();
|
|
64
|
+
const deserialized = TemporalSlice.deserialize(serialized);
|
|
65
|
+
|
|
66
|
+
assert(deserialized.data.equals(original.data), 'Data should match');
|
|
67
|
+
assert(deserialized.timestamp === original.timestamp, 'Timestamp should match');
|
|
68
|
+
assert(deserialized.sequenceNumber === original.sequenceNumber, 'Sequence should match');
|
|
69
|
+
assert(deserialized.streamId === original.streamId, 'StreamId should match');
|
|
70
|
+
assert(deserialized.temporalHash.equals(original.temporalHash), 'Temporal hash should match');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('TemporalSlice rejects tampered data', () => {
|
|
74
|
+
const slice = new TemporalSlice({
|
|
75
|
+
data: Buffer.from('Original data'),
|
|
76
|
+
timestamp: BigInt(Date.now() * 1_000_000),
|
|
77
|
+
sequenceNumber: 0,
|
|
78
|
+
streamId: 'tamper-test',
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const serialized = slice.serialize();
|
|
82
|
+
serialized.data = Buffer.from('Tampered data').toString('base64');
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
TemporalSlice.deserialize(serialized);
|
|
86
|
+
assert(false, 'Should have thrown');
|
|
87
|
+
} catch (err) {
|
|
88
|
+
assert(err.message.includes('mismatch'), 'Should detect tampering');
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('TemporalSlice chains with prevTemporalHash', () => {
|
|
93
|
+
const slice1 = new TemporalSlice({
|
|
94
|
+
data: Buffer.from('First slice'),
|
|
95
|
+
timestamp: BigInt(1000000000n),
|
|
96
|
+
sequenceNumber: 0,
|
|
97
|
+
streamId: 'chain-test',
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const slice2 = new TemporalSlice({
|
|
101
|
+
data: Buffer.from('Second slice'),
|
|
102
|
+
timestamp: BigInt(1050000000n),
|
|
103
|
+
sequenceNumber: 1,
|
|
104
|
+
streamId: 'chain-test',
|
|
105
|
+
prevTemporalHash: slice1.temporalHash,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
assert(!slice2.prevTemporalHash.equals(Buffer.alloc(32)), 'Should have prev hash');
|
|
109
|
+
assert(slice2.prevTemporalHash.equals(slice1.temporalHash), 'Prev hash should match slice1');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// =============================================================================
|
|
113
|
+
section('TemporalStream Tests');
|
|
114
|
+
// =============================================================================
|
|
115
|
+
|
|
116
|
+
test('TemporalStream encodes message into slices', () => {
|
|
117
|
+
const stream = new TemporalStream({ sliceSize: 10 });
|
|
118
|
+
const message = 'Hello, this is a test message for TME encoding!';
|
|
119
|
+
const slices = stream.encode(message);
|
|
120
|
+
|
|
121
|
+
assert(slices.length === Math.ceil(message.length / 10), 'Should create correct number of slices');
|
|
122
|
+
assert(stream.totalSlices === slices.length, 'totalSlices should match');
|
|
123
|
+
assert(stream.isComplete, 'Stream should be marked complete');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('TemporalStream maintains temporal chain integrity', () => {
|
|
127
|
+
const stream = new TemporalStream({ sliceSize: 5 });
|
|
128
|
+
const slices = stream.encode('1234567890ABCDEF');
|
|
129
|
+
|
|
130
|
+
for (let i = 1; i < slices.length; i++) {
|
|
131
|
+
const prev = slices[i - 1];
|
|
132
|
+
const curr = slices[i];
|
|
133
|
+
assert(curr.prevTemporalHash.equals(prev.temporalHash), 'Chain should be linked at slice ' + i);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('TemporalStream rejects message exceeding max slices', () => {
|
|
138
|
+
const stream = new TemporalStream({ sliceSize: 1 });
|
|
139
|
+
const hugeMessage = 'x'.repeat(TME_CONFIG.maxSlicesPerStream + 1);
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
stream.encode(hugeMessage);
|
|
143
|
+
assert(false, 'Should have thrown');
|
|
144
|
+
} catch (err) {
|
|
145
|
+
assert(err.message.includes('too large'), 'Should reject oversized message');
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('TemporalStream addSlice validates slice integrity', () => {
|
|
150
|
+
const stream = new TemporalStream({ sliceSize: 10 });
|
|
151
|
+
const slices = stream.encode('Test message');
|
|
152
|
+
|
|
153
|
+
// Create a new stream to receive
|
|
154
|
+
const receiverStream = new TemporalStream({
|
|
155
|
+
streamId: stream.streamId,
|
|
156
|
+
sliceSize: 10,
|
|
157
|
+
});
|
|
158
|
+
receiverStream.totalSlices = stream.totalSlices;
|
|
159
|
+
|
|
160
|
+
// Should accept valid slice
|
|
161
|
+
const added = receiverStream.addSlice(slices[0]);
|
|
162
|
+
assert(added, 'Should accept valid slice');
|
|
163
|
+
|
|
164
|
+
// Should reject slice from different stream
|
|
165
|
+
const wrongSlice = new TemporalSlice({
|
|
166
|
+
data: Buffer.from('Wrong'),
|
|
167
|
+
timestamp: BigInt(Date.now() * 1_000_000),
|
|
168
|
+
sequenceNumber: 1,
|
|
169
|
+
streamId: 'wrong-stream',
|
|
170
|
+
});
|
|
171
|
+
const rejected = receiverStream.addSlice(wrongSlice);
|
|
172
|
+
assert(!rejected, 'Should reject slice from wrong stream');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test('TemporalStream tracks completion percentage', () => {
|
|
176
|
+
const stream = new TemporalStream({ sliceSize: 5 });
|
|
177
|
+
const slices = stream.encode('12345678901234567890'); // 20 chars = 4 slices
|
|
178
|
+
|
|
179
|
+
const receiverStream = new TemporalStream({
|
|
180
|
+
streamId: stream.streamId,
|
|
181
|
+
sliceSize: 5,
|
|
182
|
+
});
|
|
183
|
+
receiverStream.totalSlices = 4;
|
|
184
|
+
|
|
185
|
+
assert(receiverStream.getCompletionPercent() === 0, 'Should start at 0%');
|
|
186
|
+
|
|
187
|
+
receiverStream.addSlice(slices[0]);
|
|
188
|
+
assert(receiverStream.getCompletionPercent() === 25, 'Should be 25% after 1 slice');
|
|
189
|
+
|
|
190
|
+
receiverStream.addSlice(slices[1]);
|
|
191
|
+
assert(receiverStream.getCompletionPercent() === 50, 'Should be 50% after 2 slices');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('TemporalStream detects missing slices', () => {
|
|
195
|
+
const stream = new TemporalStream({ sliceSize: 5 });
|
|
196
|
+
const slices = stream.encode('12345678901234567890');
|
|
197
|
+
|
|
198
|
+
const receiverStream = new TemporalStream({
|
|
199
|
+
streamId: stream.streamId,
|
|
200
|
+
sliceSize: 5,
|
|
201
|
+
});
|
|
202
|
+
receiverStream.totalSlices = 4;
|
|
203
|
+
|
|
204
|
+
receiverStream.addSlice(slices[0]);
|
|
205
|
+
receiverStream.addSlice(slices[3]); // Skip 1 and 2
|
|
206
|
+
|
|
207
|
+
const missing = receiverStream.getMissingSlices();
|
|
208
|
+
assert(missing.length === 2, 'Should have 2 missing slices');
|
|
209
|
+
assert(missing.includes(1), 'Should be missing slice 1');
|
|
210
|
+
assert(missing.includes(2), 'Should be missing slice 2');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// =============================================================================
|
|
214
|
+
section('TemporalReconstructor Tests');
|
|
215
|
+
// =============================================================================
|
|
216
|
+
|
|
217
|
+
test('TemporalReconstructor assembles complete stream', () => {
|
|
218
|
+
const stream = new TemporalStream({ sliceSize: 10 });
|
|
219
|
+
const message = 'Hello TME World!';
|
|
220
|
+
const slices = stream.encode(message);
|
|
221
|
+
|
|
222
|
+
const reconstructor = new TemporalReconstructor();
|
|
223
|
+
reconstructor.registerStream(stream);
|
|
224
|
+
|
|
225
|
+
const result = reconstructor.reconstruct(stream.streamId);
|
|
226
|
+
assert(result.success, 'Should reconstruct successfully');
|
|
227
|
+
assert(result.data.toString() === message, 'Data should match original');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test('TemporalReconstructor reports insufficient slices', () => {
|
|
231
|
+
const stream = new TemporalStream({ sliceSize: 5 });
|
|
232
|
+
stream.encode('12345678901234567890'); // 4 slices
|
|
233
|
+
|
|
234
|
+
// Create receiver with only 1 slice (25% < 60% threshold)
|
|
235
|
+
const receiverStream = new TemporalStream({
|
|
236
|
+
streamId: stream.streamId,
|
|
237
|
+
sliceSize: 5,
|
|
238
|
+
});
|
|
239
|
+
receiverStream.totalSlices = 4;
|
|
240
|
+
receiverStream.slices.set(0, stream.slices.get(0));
|
|
241
|
+
|
|
242
|
+
const reconstructor = new TemporalReconstructor();
|
|
243
|
+
reconstructor.registerStream(receiverStream);
|
|
244
|
+
|
|
245
|
+
const result = reconstructor.reconstruct(receiverStream.streamId);
|
|
246
|
+
assert(!result.success, 'Should fail with insufficient slices');
|
|
247
|
+
assert(result.error.includes('Insufficient'), 'Should report insufficient');
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test('TemporalReconstructor verifies missing slice via timing proofs', () => {
|
|
251
|
+
const reconstructor = new TemporalReconstructor();
|
|
252
|
+
const streamId = 'proof-test';
|
|
253
|
+
|
|
254
|
+
reconstructor.timingProofs.set(streamId, new Map());
|
|
255
|
+
|
|
256
|
+
// Add 3 timing proofs that agree on the hash
|
|
257
|
+
const consensusHash = '0'.repeat(64);
|
|
258
|
+
reconstructor.addTimingProof(streamId, 5, {
|
|
259
|
+
nodeId: 'node-a',
|
|
260
|
+
timestamp: Date.now() * 1_000_000,
|
|
261
|
+
temporalHash: consensusHash,
|
|
262
|
+
signature: 'sig-a',
|
|
263
|
+
});
|
|
264
|
+
reconstructor.addTimingProof(streamId, 5, {
|
|
265
|
+
nodeId: 'node-b',
|
|
266
|
+
timestamp: Date.now() * 1_000_000,
|
|
267
|
+
temporalHash: consensusHash,
|
|
268
|
+
signature: 'sig-b',
|
|
269
|
+
});
|
|
270
|
+
reconstructor.addTimingProof(streamId, 5, {
|
|
271
|
+
nodeId: 'node-c',
|
|
272
|
+
timestamp: Date.now() * 1_000_000,
|
|
273
|
+
temporalHash: 'f'.repeat(64), // Dissenting
|
|
274
|
+
signature: 'sig-c',
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
const verification = reconstructor.verifyMissingSlice(streamId, 5);
|
|
278
|
+
assert(verification !== null, 'Should have verification');
|
|
279
|
+
assert(verification.verified, 'Should be verified');
|
|
280
|
+
assert(verification.consensusHash === consensusHash, 'Should have consensus hash');
|
|
281
|
+
assert(verification.proofCount === 2, 'Should have 2 agreeing proofs');
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// =============================================================================
|
|
285
|
+
section('TemporalMeshEncoder Tests (End-to-End)');
|
|
286
|
+
// =============================================================================
|
|
287
|
+
|
|
288
|
+
test('TemporalMeshEncoder full encode/decode cycle', () => {
|
|
289
|
+
const sender = new TemporalMeshEncoder({ meshPosition: [1, 2, 3] });
|
|
290
|
+
const receiver = new TemporalMeshEncoder({ meshPosition: [4, 5, 6] });
|
|
291
|
+
|
|
292
|
+
const message = 'This is a complete TME transmission test!';
|
|
293
|
+
const encoded = sender.encode(message, { sliceSize: 10 });
|
|
294
|
+
|
|
295
|
+
// Receiver initializes stream
|
|
296
|
+
receiver.initReceive({
|
|
297
|
+
streamId: encoded.streamId,
|
|
298
|
+
...encoded.metadata,
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// Receive all slices
|
|
302
|
+
for (const slice of encoded.slices) {
|
|
303
|
+
receiver.receiveSlice(slice);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Decode
|
|
307
|
+
const decoded = receiver.decode(encoded.streamId);
|
|
308
|
+
assert(decoded.success, 'Should decode successfully');
|
|
309
|
+
assert(decoded.data.toString() === message, 'Message should match');
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test('TemporalMeshEncoder tracks statistics', () => {
|
|
313
|
+
const encoder = new TemporalMeshEncoder();
|
|
314
|
+
|
|
315
|
+
encoder.encode('Message 1', { sliceSize: 5 });
|
|
316
|
+
encoder.encode('Message 2', { sliceSize: 5 });
|
|
317
|
+
|
|
318
|
+
const stats = encoder.getStats();
|
|
319
|
+
assert(stats.slicesSent > 0, 'Should track slices sent');
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test('TemporalMeshEncoder handles partial reception', () => {
|
|
323
|
+
const sender = new TemporalMeshEncoder();
|
|
324
|
+
const receiver = new TemporalMeshEncoder();
|
|
325
|
+
|
|
326
|
+
const message = '12345678901234567890'; // 20 chars
|
|
327
|
+
const encoded = sender.encode(message, { sliceSize: 5 }); // 4 slices
|
|
328
|
+
|
|
329
|
+
receiver.initReceive({
|
|
330
|
+
streamId: encoded.streamId,
|
|
331
|
+
...encoded.metadata,
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Only receive slices 0 and 3 (skip 1 and 2)
|
|
335
|
+
receiver.receiveSlice(encoded.slices[0]);
|
|
336
|
+
receiver.receiveSlice(encoded.slices[3]);
|
|
337
|
+
|
|
338
|
+
const status = receiver.getStreamStatus(encoded.streamId);
|
|
339
|
+
assert(status.receivedSlices === 2, 'Should have 2 slices');
|
|
340
|
+
assert(status.completionPercent === 50, 'Should be 50% complete');
|
|
341
|
+
assert(status.missing.length === 2, 'Should have 2 missing');
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
test('TemporalMeshEncoder rejects unknown stream slice', () => {
|
|
345
|
+
const receiver = new TemporalMeshEncoder();
|
|
346
|
+
|
|
347
|
+
const fakeSlice = new TemporalSlice({
|
|
348
|
+
data: Buffer.from('Fake'),
|
|
349
|
+
timestamp: BigInt(Date.now() * 1_000_000),
|
|
350
|
+
sequenceNumber: 0,
|
|
351
|
+
streamId: 'unknown-stream',
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
const result = receiver.receiveSlice(fakeSlice.serialize());
|
|
355
|
+
assert(!result.accepted, 'Should reject unknown stream');
|
|
356
|
+
assert(result.error === 'Unknown stream', 'Should report unknown stream');
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
test('TemporalMeshEncoder detects stream completion', () => {
|
|
360
|
+
const sender = new TemporalMeshEncoder();
|
|
361
|
+
const receiver = new TemporalMeshEncoder();
|
|
362
|
+
|
|
363
|
+
const encoded = sender.encode('Short', { sliceSize: 10 }); // 1 slice
|
|
364
|
+
|
|
365
|
+
receiver.initReceive({
|
|
366
|
+
streamId: encoded.streamId,
|
|
367
|
+
...encoded.metadata,
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
const result = receiver.receiveSlice(encoded.slices[0]);
|
|
371
|
+
assert(result.streamComplete, 'Should detect completion');
|
|
372
|
+
assert(result.completionPercent === 100, 'Should be 100%');
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// =============================================================================
|
|
376
|
+
// Summary
|
|
377
|
+
// =============================================================================
|
|
378
|
+
|
|
379
|
+
console.log('\n╔══════════════════════════════════════════════════════╗');
|
|
380
|
+
console.log('║ RESULTS: ' + passed + ' passed, ' + failed + ' failed ║');
|
|
381
|
+
console.log('╚══════════════════════════════════════════════════════╝');
|
|
382
|
+
|
|
383
|
+
process.exit(failed > 0 ? 1 : 0);
|