yakmesh 1.3.1 β†’ 1.3.2

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 CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  All notable changes to YAKMESH will be documented in this file.
4
4
 
5
+ ## [1.3.2] - 2026-01-17
6
+
7
+ ### Added
8
+ - **Public Content Delivery API** - Content-addressed storage for decentralized website hosting
9
+ - `GET /content` - List available content with stats
10
+ - `GET /content/:hash` - Fetch content by hash with optional proof
11
+ - `POST /content` - Publish content with consensus verification
12
+ - Content gossip via mesh for cross-node synchronization
13
+ - Consensus proof system for verified content
14
+
15
+ ### Fixed
16
+ - Gossip protocol method calls (use `spreadRumor()` instead of `broadcast()`)
17
+ - Direct messaging via mesh instead of non-existent gossip.sendTo()
18
+
19
+ ### Community
20
+ - Added social links: Discord, Telegram, X (Twitter)
21
+ - Created Discord announcement template
22
+
23
+ ---
24
+
5
25
  ## [1.3.1] - 2026-01-16
6
26
 
7
27
  ### Security
package/README.md CHANGED
@@ -179,6 +179,12 @@ See [TRADEMARK.md](TRADEMARK.md) for trademark usage policy.
179
179
  <br><br>
180
180
  <strong><a href="https://yakmesh.dev">yakmesh.dev</a></strong>
181
181
  <br><br>
182
+ <p>
183
+ <a href="https://discord.gg/E62tAE2wGh">πŸ’¬ Discord</a> β€’
184
+ <a href="https://t.me/yakmesh">πŸ“± Telegram</a> β€’
185
+ <a href="https://x.com/yakmesh">𝕏 Twitter</a>
186
+ </p>
187
+ <br>
182
188
  <sub>Β© 2026 YAKMESHβ„’ Project. Sturdy & Secure.</sub>
183
189
  <br>
184
190
  <sub>YAKMESHβ„’ is a trademark of PeerQuanta, application pending (Serial No. 99594620).</sub>
@@ -0,0 +1,55 @@
1
+ # 🦬 YAKMESH v1.3.1 β€” Public Content Delivery + Mesh Peering Confirmed!
2
+
3
+ Hey everyone! Big update today:
4
+
5
+ ## βœ… What's New
6
+
7
+ ### 🌐 Public Content Delivery API
8
+ We've added a complete **content-addressed storage system** with public delivery:
9
+
10
+ ```
11
+ GET /content/:hash β†’ Fetch any content by its hash
12
+ GET /content/:hash/proof β†’ Get consensus proof for verification
13
+ POST /content/publish β†’ Store and gossip content to mesh
14
+ ```
15
+
16
+ **Key features:**
17
+ - Content addressed by SHA3-256 hash (trustless verification)
18
+ - Consensus proofs for light client verification
19
+ - LRU caching for instant edge delivery
20
+ - Automatic mesh sync via gossip protocol
21
+
22
+ ### πŸ”— First Successful LAN Mesh Peering
23
+ Tested and confirmed: **two Yakmesh nodes successfully peered** with matching network fingerprints. The Code Proof Protocol verified both were running identical codebases before allowing the connection.
24
+
25
+ **Connection is as simple as:**
26
+ ```powershell
27
+ POST http://localhost:3000/connect
28
+ { "address": "ws://192.168.1.178:9001" }
29
+ ```
30
+
31
+ ### πŸ“± New Social Channels
32
+ We're now on:
33
+ - πŸ’¬ **Discord**: https://discord.gg/E62tAE2wGh
34
+ - πŸ“± **Telegram**: https://t.me/yakmesh
35
+ - 𝕏 **Twitter**: https://x.com/yakmesh
36
+
37
+ ## πŸ“¦ Install
38
+
39
+ ```bash
40
+ npm install yakmesh@1.3.1
41
+ ```
42
+
43
+ ## πŸ”— Links
44
+ - 🌐 Website: https://yakmesh.dev
45
+ - πŸ“– GitHub: https://github.com/yakmesh/yakmesh
46
+ - πŸ“¦ npm: https://npmjs.com/package/yakmesh
47
+
48
+ ---
49
+
50
+ **What's next?**
51
+ - Multi-node cluster testing
52
+ - Production deployment
53
+ - Website/webapp hosting demos
54
+
55
+ Questions? Drop them here! 🦬
package/content/api.js ADDED
@@ -0,0 +1,348 @@
1
+ /**
2
+ * YAKMESHβ„’ Public Content API
3
+ * HTTP endpoints for public content delivery
4
+ *
5
+ * Public (no auth required):
6
+ * - GET /content/:hash - Fetch content by hash
7
+ * - GET /content/:hash/meta - Fetch metadata only
8
+ * - GET /content/:hash/proof - Fetch consensus proof
9
+ * - GET /content/list - List available content
10
+ *
11
+ * Authenticated (rate limited):
12
+ * - POST /content/publish - Publish new content
13
+ * - DELETE /content/:hash - Remove content (owner only)
14
+ *
15
+ * @module content/api
16
+ * @license MIT
17
+ * @copyright 2026 YAKMESH Contributors
18
+ */
19
+
20
+ import { Router } from 'express';
21
+ import { ContentStore, ContentType, ContentStatus, computeContentHash } from './store.js';
22
+
23
+ /**
24
+ * Create content API router
25
+ */
26
+ export function createContentAPI(contentStore, options = {}) {
27
+ const router = Router();
28
+
29
+ const {
30
+ writeLimiter,
31
+ readLimiter,
32
+ validateString,
33
+ } = options;
34
+
35
+ // =========================================
36
+ // PUBLIC READ ENDPOINTS (No Auth)
37
+ // =========================================
38
+
39
+ /**
40
+ * GET /content/:hash
41
+ * Fetch content by hash with optional proof
42
+ *
43
+ * Query params:
44
+ * - proof=1 : Include consensus proof in response headers
45
+ * - download=1 : Force download (Content-Disposition)
46
+ */
47
+ router.get('/:hash', readLimiter, (req, res) => {
48
+ const { hash } = req.params;
49
+ const includeProof = req.query.proof === '1';
50
+ const download = req.query.download === '1';
51
+
52
+ // Get content with metadata
53
+ const result = contentStore.getWithProof(hash);
54
+
55
+ if (!result) {
56
+ return res.status(404).json({
57
+ error: 'Content not found',
58
+ hash,
59
+ hint: 'Content may not have synced yet. Try again later.',
60
+ });
61
+ }
62
+
63
+ // Set content type
64
+ res.setHeader('Content-Type', result.meta?.contentType || 'application/octet-stream');
65
+ res.setHeader('Content-Length', result.meta?.size || result.content.length);
66
+ res.setHeader('X-Content-Hash', result.hash);
67
+ res.setHeader('X-Content-Status', result.meta?.status || 'unknown');
68
+
69
+ // Cache headers (immutable content = cache forever)
70
+ if (result.verified) {
71
+ res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
72
+ } else {
73
+ res.setHeader('Cache-Control', 'public, max-age=60');
74
+ }
75
+
76
+ // Include proof in headers if requested
77
+ if (includeProof && result.proof) {
78
+ res.setHeader('X-Consensus-Proof', JSON.stringify(result.proof));
79
+ res.setHeader('X-Verified', result.verified ? 'true' : 'false');
80
+ }
81
+
82
+ // Download disposition
83
+ if (download && result.meta?.name) {
84
+ res.setHeader('Content-Disposition', `attachment; filename="${result.meta.name}"`);
85
+ }
86
+
87
+ // Send content
88
+ res.send(result.content);
89
+ });
90
+
91
+ /**
92
+ * GET /content/:hash/meta
93
+ * Fetch metadata only (no content body)
94
+ */
95
+ router.get('/:hash/meta', readLimiter, (req, res) => {
96
+ const { hash } = req.params;
97
+ const meta = contentStore.getMeta(hash);
98
+
99
+ if (!meta) {
100
+ return res.status(404).json({ error: 'Content not found', hash });
101
+ }
102
+
103
+ res.json(meta.toJSON ? meta.toJSON() : meta);
104
+ });
105
+
106
+ /**
107
+ * GET /content/:hash/proof
108
+ * Fetch consensus proof for light client verification
109
+ */
110
+ router.get('/:hash/proof', readLimiter, (req, res) => {
111
+ const { hash } = req.params;
112
+ const meta = contentStore.getMeta(hash);
113
+
114
+ if (!meta) {
115
+ return res.status(404).json({ error: 'Content not found', hash });
116
+ }
117
+
118
+ if (!meta.consensusProof) {
119
+ return res.status(404).json({
120
+ error: 'No consensus proof yet',
121
+ hash,
122
+ status: meta.status,
123
+ hint: 'Content may still be pending consensus.',
124
+ });
125
+ }
126
+
127
+ res.json({
128
+ hash,
129
+ verified: meta.status === ContentStatus.VERIFIED,
130
+ proof: meta.consensusProof.toJSON ? meta.consensusProof.toJSON() : meta.consensusProof,
131
+ });
132
+ });
133
+
134
+ /**
135
+ * GET /content/list
136
+ * List available content
137
+ *
138
+ * Query params:
139
+ * - tag=<tag> : Filter by tag
140
+ * - status=<status> : Filter by status (local, pending, verified)
141
+ * - limit=<n> : Max results (default 100)
142
+ * - offset=<n> : Pagination offset
143
+ */
144
+ router.get('/', readLimiter, (req, res) => {
145
+ const { tag, status, limit = 100, offset = 0 } = req.query;
146
+
147
+ const items = contentStore.list({
148
+ tag,
149
+ status,
150
+ limit: Math.min(parseInt(limit) || 100, 1000),
151
+ offset: parseInt(offset) || 0,
152
+ });
153
+
154
+ res.json({
155
+ items,
156
+ count: items.length,
157
+ stats: contentStore.getStats(),
158
+ });
159
+ });
160
+
161
+ /**
162
+ * HEAD /content/:hash
163
+ * Check if content exists (useful for CDN/cache validation)
164
+ */
165
+ router.head('/:hash', readLimiter, (req, res) => {
166
+ const { hash } = req.params;
167
+
168
+ if (contentStore.has(hash)) {
169
+ const meta = contentStore.getMeta(hash);
170
+ res.setHeader('Content-Type', meta?.contentType || 'application/octet-stream');
171
+ res.setHeader('Content-Length', meta?.size || 0);
172
+ res.setHeader('X-Content-Hash', hash);
173
+ res.setHeader('X-Content-Status', meta?.status || 'unknown');
174
+ res.status(200).end();
175
+ } else {
176
+ res.status(404).end();
177
+ }
178
+ });
179
+
180
+ // =========================================
181
+ // AUTHENTICATED WRITE ENDPOINTS
182
+ // =========================================
183
+
184
+ /**
185
+ * POST /content/publish
186
+ * Publish new content to the mesh
187
+ *
188
+ * Body (JSON):
189
+ * {
190
+ * content: <string|object>,
191
+ * contentType?: <mime-type>,
192
+ * name?: <human-readable-name>,
193
+ * tags?: [<tag>, ...],
194
+ * ttl?: <seconds>
195
+ * }
196
+ *
197
+ * Body (multipart/form-data):
198
+ * - file: uploaded file
199
+ * - name: optional name
200
+ * - tags: comma-separated tags
201
+ */
202
+ router.post('/publish', writeLimiter, async (req, res) => {
203
+ try {
204
+ let content;
205
+ let options = {};
206
+
207
+ // Handle JSON body
208
+ if (req.is('application/json')) {
209
+ if (!req.body.content) {
210
+ return res.status(400).json({ error: 'Content required' });
211
+ }
212
+ content = req.body.content;
213
+ options = {
214
+ contentType: req.body.contentType,
215
+ name: req.body.name,
216
+ tags: req.body.tags || [],
217
+ ttl: req.body.ttl || 0,
218
+ };
219
+ }
220
+ // Handle raw body
221
+ else if (req.body && Buffer.isBuffer(req.body)) {
222
+ content = req.body;
223
+ options = {
224
+ contentType: req.headers['content-type'] || 'application/octet-stream',
225
+ name: req.headers['x-content-name'],
226
+ tags: req.headers['x-content-tags']?.split(',') || [],
227
+ };
228
+ }
229
+ // Handle form data (basic - for full multipart use multer)
230
+ else if (req.body?.content) {
231
+ content = req.body.content;
232
+ options = {
233
+ contentType: req.body.contentType,
234
+ name: req.body.name,
235
+ tags: typeof req.body.tags === 'string' ? req.body.tags.split(',') : req.body.tags,
236
+ };
237
+ }
238
+ else {
239
+ return res.status(400).json({ error: 'Content required in body' });
240
+ }
241
+
242
+ // Store and publish
243
+ const result = await contentStore.store(content, options);
244
+
245
+ res.status(201).json({
246
+ success: true,
247
+ hash: result.hash,
248
+ status: result.status,
249
+ meta: result.meta?.toJSON ? result.meta.toJSON() : result.meta,
250
+ url: `/content/${result.hash}`,
251
+ });
252
+ } catch (error) {
253
+ res.status(500).json({ error: error.message });
254
+ }
255
+ });
256
+
257
+ /**
258
+ * POST /content/request
259
+ * Request content from mesh (if not locally available)
260
+ */
261
+ router.post('/request', writeLimiter, async (req, res) => {
262
+ const { hash } = req.body;
263
+
264
+ if (!hash) {
265
+ return res.status(400).json({ error: 'Hash required' });
266
+ }
267
+
268
+ try {
269
+ // Check local first
270
+ if (contentStore.has(hash)) {
271
+ return res.json({
272
+ found: true,
273
+ local: true,
274
+ hash,
275
+ });
276
+ }
277
+
278
+ // Request from mesh
279
+ const result = await contentStore.request(hash);
280
+
281
+ res.json({
282
+ found: true,
283
+ local: false,
284
+ hash: result.hash,
285
+ meta: result.meta,
286
+ });
287
+ } catch (error) {
288
+ res.status(404).json({
289
+ found: false,
290
+ hash,
291
+ error: error.message,
292
+ });
293
+ }
294
+ });
295
+
296
+ /**
297
+ * DELETE /content/:hash
298
+ * Remove content (local only - cannot remove from mesh)
299
+ */
300
+ router.delete('/:hash', writeLimiter, (req, res) => {
301
+ const { hash } = req.params;
302
+
303
+ if (!contentStore.has(hash)) {
304
+ return res.status(404).json({ error: 'Content not found', hash });
305
+ }
306
+
307
+ // Note: This only removes locally - content may still exist on other nodes
308
+ contentStore.delete(hash);
309
+
310
+ res.json({
311
+ deleted: true,
312
+ hash,
313
+ note: 'Content removed locally. Other mesh nodes may still have copies.',
314
+ });
315
+ });
316
+
317
+ /**
318
+ * GET /content/stats
319
+ * Get content store statistics
320
+ */
321
+ router.get('/stats', readLimiter, (req, res) => {
322
+ res.json(contentStore.getStats());
323
+ });
324
+
325
+ /**
326
+ * POST /content/verify
327
+ * Compute hash for content without storing
328
+ * Useful for clients to verify content integrity
329
+ */
330
+ router.post('/verify', readLimiter, (req, res) => {
331
+ const content = req.body.content || req.body;
332
+
333
+ if (!content) {
334
+ return res.status(400).json({ error: 'Content required' });
335
+ }
336
+
337
+ const hash = computeContentHash(content);
338
+
339
+ res.json({
340
+ hash,
341
+ exists: contentStore.has(hash),
342
+ });
343
+ });
344
+
345
+ return router;
346
+ }
347
+
348
+ export default createContentAPI;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * YAKMESHβ„’ Content Module
3
+ * Content-addressed storage with public delivery
4
+ *
5
+ * @module content
6
+ * @license MIT
7
+ * @copyright 2026 YAKMESH Contributors
8
+ */
9
+
10
+ export {
11
+ ContentStore,
12
+ ContentType,
13
+ ContentStatus,
14
+ ContentMetadata,
15
+ ConsensusProof,
16
+ computeContentHash,
17
+ } from './store.js';
18
+
19
+ export { createContentAPI } from './api.js';
@@ -0,0 +1,670 @@
1
+ /**
2
+ * YAKMESHβ„’ Content Store
3
+ * Content-addressed storage with consensus proofs
4
+ *
5
+ * Provides public content delivery while maintaining mesh security:
6
+ * - Content addressed by hash (trustless verification)
7
+ * - Consensus proofs for light client verification
8
+ * - Edge caching for instant public access
9
+ * - Mesh sync for decentralized replication
10
+ *
11
+ * @module content/store
12
+ * @license MIT
13
+ * @copyright 2026 YAKMESH Contributors
14
+ */
15
+
16
+ import { sha3_256 } from '@noble/hashes/sha3.js';
17
+ import { bytesToHex, utf8ToBytes } from '@noble/hashes/utils.js';
18
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, unlinkSync, statSync } from 'fs';
19
+ import { join, dirname } from 'path';
20
+
21
+ /**
22
+ * Content types supported
23
+ */
24
+ export const ContentType = {
25
+ JSON: 'application/json',
26
+ HTML: 'text/html',
27
+ TEXT: 'text/plain',
28
+ BINARY: 'application/octet-stream',
29
+ JAVASCRIPT: 'application/javascript',
30
+ CSS: 'text/css',
31
+ IMAGE_PNG: 'image/png',
32
+ IMAGE_JPG: 'image/jpeg',
33
+ IMAGE_SVG: 'image/svg+xml',
34
+ };
35
+
36
+ /**
37
+ * Content status in the network
38
+ */
39
+ export const ContentStatus = {
40
+ LOCAL: 'local', // Only on this node
41
+ PENDING: 'pending', // Awaiting consensus
42
+ VERIFIED: 'verified', // Consensus reached
43
+ REJECTED: 'rejected', // Failed consensus
44
+ };
45
+
46
+ /**
47
+ * Compute content hash (SHA3-256)
48
+ */
49
+ export function computeContentHash(content) {
50
+ if (typeof content === 'string') {
51
+ return bytesToHex(sha3_256(utf8ToBytes(content)));
52
+ }
53
+ if (Buffer.isBuffer(content)) {
54
+ return bytesToHex(sha3_256(new Uint8Array(content)));
55
+ }
56
+ if (content instanceof Uint8Array) {
57
+ return bytesToHex(sha3_256(content));
58
+ }
59
+ // Object - serialize deterministically
60
+ return bytesToHex(sha3_256(utf8ToBytes(JSON.stringify(content))));
61
+ }
62
+
63
+ /**
64
+ * Content metadata
65
+ */
66
+ class ContentMetadata {
67
+ constructor(options = {}) {
68
+ this.hash = options.hash;
69
+ this.contentType = options.contentType || ContentType.BINARY;
70
+ this.size = options.size || 0;
71
+ this.createdAt = options.createdAt || Date.now();
72
+ this.publishedBy = options.publishedBy || null;
73
+ this.status = options.status || ContentStatus.LOCAL;
74
+ this.consensusProof = options.consensusProof || null;
75
+ this.tags = options.tags || [];
76
+ this.name = options.name || null; // Optional human-readable name
77
+ this.ttl = options.ttl || 0; // 0 = permanent
78
+ }
79
+
80
+ toJSON() {
81
+ return {
82
+ hash: this.hash,
83
+ contentType: this.contentType,
84
+ size: this.size,
85
+ createdAt: this.createdAt,
86
+ publishedBy: this.publishedBy,
87
+ status: this.status,
88
+ consensusProof: this.consensusProof,
89
+ tags: this.tags,
90
+ name: this.name,
91
+ ttl: this.ttl,
92
+ };
93
+ }
94
+
95
+ static fromJSON(json) {
96
+ return new ContentMetadata(json);
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Consensus proof for light client verification
102
+ */
103
+ class ConsensusProof {
104
+ constructor(options = {}) {
105
+ this.contentHash = options.contentHash;
106
+ this.timestamp = options.timestamp || Date.now();
107
+ this.validators = options.validators || []; // Array of { nodeId, signature }
108
+ this.quorum = options.quorum || 0; // Required signatures
109
+ this.networkId = options.networkId || null;
110
+ }
111
+
112
+ /**
113
+ * Check if proof has quorum
114
+ */
115
+ hasQuorum() {
116
+ return this.validators.length >= this.quorum;
117
+ }
118
+
119
+ /**
120
+ * Add validator signature
121
+ */
122
+ addValidator(nodeId, signature) {
123
+ if (!this.validators.find(v => v.nodeId === nodeId)) {
124
+ this.validators.push({ nodeId, signature, timestamp: Date.now() });
125
+ }
126
+ }
127
+
128
+ toJSON() {
129
+ return {
130
+ contentHash: this.contentHash,
131
+ timestamp: this.timestamp,
132
+ validators: this.validators,
133
+ quorum: this.quorum,
134
+ networkId: this.networkId,
135
+ };
136
+ }
137
+
138
+ static fromJSON(json) {
139
+ return new ConsensusProof(json);
140
+ }
141
+ }
142
+
143
+ /**
144
+ * YAKMESH Content Store
145
+ * Content-addressed storage with mesh sync and public delivery
146
+ */
147
+ export class ContentStore {
148
+ constructor(config = {}) {
149
+ this.config = {
150
+ dataDir: config.dataDir || './data/content',
151
+ maxContentSize: config.maxContentSize || 10 * 1024 * 1024, // 10MB default
152
+ cacheSize: config.cacheSize || 100, // LRU cache entries
153
+ quorumSize: config.quorumSize || 2, // Minimum validators
154
+ ...config,
155
+ };
156
+
157
+ this.contentDir = join(this.config.dataDir, 'objects');
158
+ this.metaDir = join(this.config.dataDir, 'meta');
159
+
160
+ // In-memory caches
161
+ this.contentCache = new Map(); // hash -> content (LRU)
162
+ this.metaCache = new Map(); // hash -> ContentMetadata
163
+ this.nameIndex = new Map(); // name -> hash (for human-readable lookup)
164
+
165
+ // Mesh integration (set by init)
166
+ this.mesh = null;
167
+ this.identity = null;
168
+ this.oracle = null;
169
+ this.gossip = null;
170
+ }
171
+
172
+ /**
173
+ * Initialize the content store
174
+ */
175
+ async init(node = null) {
176
+ // Create directories
177
+ mkdirSync(this.contentDir, { recursive: true });
178
+ mkdirSync(this.metaDir, { recursive: true });
179
+
180
+ // Load existing metadata into cache
181
+ this._loadMetadataIndex();
182
+
183
+ // Integrate with node if provided
184
+ if (node) {
185
+ this.mesh = node.mesh;
186
+ this.identity = node.identity;
187
+ this.oracle = node.oracle;
188
+ this.gossip = node.gossip;
189
+
190
+ // Content gossip is handled by the server via mesh.on('rumor')
191
+ // which calls contentStore._handleContentGossip()
192
+ }
193
+
194
+ console.log(`βœ“ Content store initialized: ${this.config.dataDir}`);
195
+ console.log(` Objects: ${this.metaCache.size}`);
196
+
197
+ return this;
198
+ }
199
+
200
+ /**
201
+ * Load metadata index from disk
202
+ */
203
+ _loadMetadataIndex() {
204
+ if (!existsSync(this.metaDir)) return;
205
+
206
+ const files = readdirSync(this.metaDir);
207
+ for (const file of files) {
208
+ if (!file.endsWith('.json')) continue;
209
+ try {
210
+ const metaPath = join(this.metaDir, file);
211
+ const json = JSON.parse(readFileSync(metaPath, 'utf8'));
212
+ const meta = ContentMetadata.fromJSON(json);
213
+ this.metaCache.set(meta.hash, meta);
214
+ if (meta.name) {
215
+ this.nameIndex.set(meta.name, meta.hash);
216
+ }
217
+ } catch (e) {
218
+ console.warn(`Failed to load metadata: ${file}`);
219
+ }
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Get content path for a hash
225
+ */
226
+ _getContentPath(hash) {
227
+ // Store in subdirectories for filesystem efficiency (git-style)
228
+ const prefix = hash.slice(0, 2);
229
+ const suffix = hash.slice(2);
230
+ return join(this.contentDir, prefix, suffix);
231
+ }
232
+
233
+ /**
234
+ * Get metadata path for a hash
235
+ */
236
+ _getMetaPath(hash) {
237
+ return join(this.metaDir, `${hash}.json`);
238
+ }
239
+
240
+ /**
241
+ * Store content
242
+ */
243
+ async store(content, options = {}) {
244
+ // Compute hash
245
+ const hash = computeContentHash(content);
246
+
247
+ // Check size limit
248
+ const size = Buffer.isBuffer(content) ? content.length :
249
+ typeof content === 'string' ? Buffer.byteLength(content) :
250
+ Buffer.byteLength(JSON.stringify(content));
251
+
252
+ if (size > this.config.maxContentSize) {
253
+ throw new Error(`Content exceeds max size: ${size} > ${this.config.maxContentSize}`);
254
+ }
255
+
256
+ // Check if already exists
257
+ if (this.has(hash)) {
258
+ const existing = this.getMeta(hash);
259
+ return { hash, status: 'exists', meta: existing };
260
+ }
261
+
262
+ // Create metadata
263
+ const meta = new ContentMetadata({
264
+ hash,
265
+ contentType: options.contentType || this._detectContentType(content),
266
+ size,
267
+ publishedBy: this.identity?.identity?.nodeId || options.publishedBy || 'unknown',
268
+ status: ContentStatus.LOCAL,
269
+ tags: options.tags || [],
270
+ name: options.name || null,
271
+ ttl: options.ttl || 0,
272
+ });
273
+
274
+ // Write content to disk
275
+ const contentPath = this._getContentPath(hash);
276
+ mkdirSync(dirname(contentPath), { recursive: true });
277
+
278
+ if (Buffer.isBuffer(content)) {
279
+ writeFileSync(contentPath, content);
280
+ } else if (typeof content === 'string') {
281
+ writeFileSync(contentPath, content, 'utf8');
282
+ } else {
283
+ writeFileSync(contentPath, JSON.stringify(content), 'utf8');
284
+ }
285
+
286
+ // Write metadata
287
+ writeFileSync(this._getMetaPath(hash), JSON.stringify(meta.toJSON(), null, 2));
288
+
289
+ // Update caches
290
+ this.metaCache.set(hash, meta);
291
+ if (meta.name) {
292
+ this.nameIndex.set(meta.name, hash);
293
+ }
294
+ this._addToContentCache(hash, content);
295
+
296
+ // Gossip to mesh
297
+ if (this.gossip && options.publish !== false) {
298
+ await this.publish(hash);
299
+ }
300
+
301
+ return { hash, status: 'stored', meta };
302
+ }
303
+
304
+ /**
305
+ * Retrieve content by hash
306
+ */
307
+ get(hash) {
308
+ // Resolve name to hash if needed
309
+ if (!hash.match(/^[a-f0-9]{64}$/i)) {
310
+ hash = this.nameIndex.get(hash) || hash;
311
+ }
312
+
313
+ // Check memory cache
314
+ if (this.contentCache.has(hash)) {
315
+ return this.contentCache.get(hash);
316
+ }
317
+
318
+ // Check disk
319
+ const contentPath = this._getContentPath(hash);
320
+ if (!existsSync(contentPath)) {
321
+ return null;
322
+ }
323
+
324
+ // Load and cache
325
+ const content = readFileSync(contentPath);
326
+ this._addToContentCache(hash, content);
327
+
328
+ return content;
329
+ }
330
+
331
+ /**
332
+ * Get content with metadata and proof
333
+ */
334
+ getWithProof(hash) {
335
+ const content = this.get(hash);
336
+ if (!content) return null;
337
+
338
+ const meta = this.getMeta(hash);
339
+
340
+ return {
341
+ content,
342
+ hash,
343
+ meta: meta?.toJSON() || null,
344
+ proof: meta?.consensusProof || null,
345
+ verified: meta?.status === ContentStatus.VERIFIED,
346
+ };
347
+ }
348
+
349
+ /**
350
+ * Get metadata for content
351
+ */
352
+ getMeta(hash) {
353
+ // Resolve name if needed
354
+ if (!hash.match(/^[a-f0-9]{64}$/i)) {
355
+ hash = this.nameIndex.get(hash) || hash;
356
+ }
357
+ return this.metaCache.get(hash) || null;
358
+ }
359
+
360
+ /**
361
+ * Check if content exists
362
+ */
363
+ has(hash) {
364
+ // Resolve name if needed
365
+ if (!hash.match(/^[a-f0-9]{64}$/i)) {
366
+ hash = this.nameIndex.get(hash) || hash;
367
+ }
368
+ return this.metaCache.has(hash) || existsSync(this._getContentPath(hash));
369
+ }
370
+
371
+ /**
372
+ * Delete content
373
+ */
374
+ delete(hash) {
375
+ const meta = this.getMeta(hash);
376
+
377
+ // Remove from disk
378
+ const contentPath = this._getContentPath(hash);
379
+ const metaPath = this._getMetaPath(hash);
380
+
381
+ if (existsSync(contentPath)) unlinkSync(contentPath);
382
+ if (existsSync(metaPath)) unlinkSync(metaPath);
383
+
384
+ // Remove from caches
385
+ this.contentCache.delete(hash);
386
+ this.metaCache.delete(hash);
387
+ if (meta?.name) {
388
+ this.nameIndex.delete(meta.name);
389
+ }
390
+
391
+ return true;
392
+ }
393
+
394
+ /**
395
+ * List all content
396
+ */
397
+ list(options = {}) {
398
+ const { tag, status, limit = 100, offset = 0 } = options;
399
+
400
+ let items = Array.from(this.metaCache.values());
401
+
402
+ // Filter by tag
403
+ if (tag) {
404
+ items = items.filter(m => m.tags.includes(tag));
405
+ }
406
+
407
+ // Filter by status
408
+ if (status) {
409
+ items = items.filter(m => m.status === status);
410
+ }
411
+
412
+ // Sort by created date (newest first)
413
+ items.sort((a, b) => b.createdAt - a.createdAt);
414
+
415
+ // Paginate
416
+ return items.slice(offset, offset + limit).map(m => m.toJSON());
417
+ }
418
+
419
+ /**
420
+ * Publish content to mesh
421
+ */
422
+ async publish(hash) {
423
+ const meta = this.getMeta(hash);
424
+ if (!meta) {
425
+ throw new Error(`Content not found: ${hash}`);
426
+ }
427
+
428
+ // Create announcement message
429
+ const announcement = {
430
+ type: 'content_announce',
431
+ hash,
432
+ meta: {
433
+ contentType: meta.contentType,
434
+ size: meta.size,
435
+ publishedBy: meta.publishedBy,
436
+ tags: meta.tags,
437
+ name: meta.name,
438
+ },
439
+ timestamp: Date.now(),
440
+ };
441
+
442
+ // Sign with node identity
443
+ if (this.identity) {
444
+ announcement.signature = this.identity.sign(JSON.stringify(announcement));
445
+ }
446
+
447
+ // Gossip to mesh
448
+ if (this.gossip) {
449
+ this.gossip.spreadRumor('content', announcement);
450
+ }
451
+
452
+ // Update status
453
+ meta.status = ContentStatus.PENDING;
454
+ writeFileSync(this._getMetaPath(hash), JSON.stringify(meta.toJSON(), null, 2));
455
+
456
+ return { published: true, hash };
457
+ }
458
+
459
+ /**
460
+ * Request content from mesh
461
+ */
462
+ async request(hash) {
463
+ if (this.has(hash)) {
464
+ return this.getWithProof(hash);
465
+ }
466
+
467
+ // Broadcast request
468
+ if (this.gossip) {
469
+ this.gossip.spreadRumor('content', {
470
+ type: 'content_request',
471
+ hash,
472
+ requestedBy: this.identity?.identity?.nodeId,
473
+ timestamp: Date.now(),
474
+ });
475
+ }
476
+
477
+ // Wait for response (with timeout)
478
+ return new Promise((resolve, reject) => {
479
+ const timeout = setTimeout(() => {
480
+ reject(new Error(`Content not found: ${hash}`));
481
+ }, 10000);
482
+
483
+ const checkInterval = setInterval(() => {
484
+ if (this.has(hash)) {
485
+ clearTimeout(timeout);
486
+ clearInterval(checkInterval);
487
+ resolve(this.getWithProof(hash));
488
+ }
489
+ }, 500);
490
+ });
491
+ }
492
+
493
+ /**
494
+ * Handle content gossip from peers
495
+ */
496
+ async _handleContentGossip(data, origin) {
497
+ switch (data.type) {
498
+ case 'content_announce':
499
+ // Peer has new content - request it if we don't have it
500
+ if (!this.has(data.hash)) {
501
+ console.log(`πŸ“¦ New content announced: ${data.hash.slice(0, 16)}... from ${origin.slice(0, 16)}...`);
502
+ // Request full content via mesh
503
+ if (this.mesh) {
504
+ this.mesh.sendTo(origin, {
505
+ type: 'content_request',
506
+ hash: data.hash,
507
+ requestedBy: this.identity?.identity?.nodeId,
508
+ });
509
+ }
510
+ }
511
+ break;
512
+
513
+ case 'content_request':
514
+ // Peer wants content - send if we have it
515
+ if (this.has(data.hash)) {
516
+ const result = this.getWithProof(data.hash);
517
+ if (this.mesh && result) {
518
+ this.mesh.sendTo(origin, {
519
+ type: 'content_response',
520
+ hash: data.hash,
521
+ content: result.content.toString('base64'),
522
+ meta: result.meta,
523
+ proof: result.proof,
524
+ });
525
+ }
526
+ }
527
+ break;
528
+
529
+ case 'content_response':
530
+ // Received content from peer
531
+ if (!this.has(data.hash)) {
532
+ const content = Buffer.from(data.content, 'base64');
533
+ const computedHash = computeContentHash(content);
534
+
535
+ // Verify hash
536
+ if (computedHash !== data.hash) {
537
+ console.warn(`⚠️ Content hash mismatch from ${origin.slice(0, 16)}...`);
538
+ return;
539
+ }
540
+
541
+ // Store it
542
+ await this.store(content, {
543
+ ...data.meta,
544
+ publish: false, // Don't re-gossip
545
+ });
546
+
547
+ // Apply consensus proof if present
548
+ if (data.proof) {
549
+ const meta = this.getMeta(data.hash);
550
+ meta.consensusProof = ConsensusProof.fromJSON(data.proof);
551
+ meta.status = data.proof.hasQuorum?.() ? ContentStatus.VERIFIED : ContentStatus.PENDING;
552
+ writeFileSync(this._getMetaPath(data.hash), JSON.stringify(meta.toJSON(), null, 2));
553
+ }
554
+
555
+ console.log(`βœ“ Content received: ${data.hash.slice(0, 16)}...`);
556
+ }
557
+ break;
558
+
559
+ case 'content_validate':
560
+ // Peer is requesting validation vote
561
+ if (this.has(data.hash) && this.identity && this.oracle) {
562
+ const content = this.get(data.hash);
563
+ const isValid = this.oracle.validateContent(content, data.contentType);
564
+
565
+ if (isValid) {
566
+ // Sign validation
567
+ const vote = {
568
+ type: 'content_vote',
569
+ hash: data.hash,
570
+ nodeId: this.identity.identity.nodeId,
571
+ vote: 'valid',
572
+ signature: this.identity.sign(data.hash),
573
+ timestamp: Date.now(),
574
+ };
575
+ this.gossip.spreadRumor('content', vote);
576
+ }
577
+ }
578
+ break;
579
+
580
+ case 'content_vote':
581
+ // Received validation vote
582
+ const meta = this.getMeta(data.hash);
583
+ if (meta) {
584
+ if (!meta.consensusProof) {
585
+ meta.consensusProof = new ConsensusProof({
586
+ contentHash: data.hash,
587
+ quorum: this.config.quorumSize,
588
+ networkId: this.mesh?.networkId,
589
+ });
590
+ }
591
+ meta.consensusProof.addValidator(data.nodeId, data.signature);
592
+
593
+ if (meta.consensusProof.hasQuorum()) {
594
+ meta.status = ContentStatus.VERIFIED;
595
+ console.log(`βœ“ Content verified (quorum reached): ${data.hash.slice(0, 16)}...`);
596
+ }
597
+
598
+ writeFileSync(this._getMetaPath(data.hash), JSON.stringify(meta.toJSON(), null, 2));
599
+ }
600
+ break;
601
+ }
602
+ }
603
+
604
+ /**
605
+ * Add to LRU content cache
606
+ */
607
+ _addToContentCache(hash, content) {
608
+ // Simple LRU: remove oldest if at capacity
609
+ if (this.contentCache.size >= this.config.cacheSize) {
610
+ const oldest = this.contentCache.keys().next().value;
611
+ this.contentCache.delete(oldest);
612
+ }
613
+ this.contentCache.set(hash, content);
614
+ }
615
+
616
+ /**
617
+ * Detect content type from content
618
+ */
619
+ _detectContentType(content) {
620
+ if (typeof content === 'object' && !Buffer.isBuffer(content)) {
621
+ return ContentType.JSON;
622
+ }
623
+
624
+ const str = content.toString().slice(0, 100);
625
+
626
+ if (str.startsWith('<!DOCTYPE') || str.startsWith('<html')) {
627
+ return ContentType.HTML;
628
+ }
629
+ if (str.startsWith('{') || str.startsWith('[')) {
630
+ return ContentType.JSON;
631
+ }
632
+ if (str.includes('function') || str.includes('const ') || str.includes('import ')) {
633
+ return ContentType.JAVASCRIPT;
634
+ }
635
+
636
+ return ContentType.TEXT;
637
+ }
638
+
639
+ /**
640
+ * Get store statistics
641
+ */
642
+ getStats() {
643
+ let totalSize = 0;
644
+ let verified = 0;
645
+ let pending = 0;
646
+ let local = 0;
647
+
648
+ for (const meta of this.metaCache.values()) {
649
+ totalSize += meta.size;
650
+ switch (meta.status) {
651
+ case ContentStatus.VERIFIED: verified++; break;
652
+ case ContentStatus.PENDING: pending++; break;
653
+ case ContentStatus.LOCAL: local++; break;
654
+ }
655
+ }
656
+
657
+ return {
658
+ totalObjects: this.metaCache.size,
659
+ totalSize,
660
+ verified,
661
+ pending,
662
+ local,
663
+ cacheSize: this.contentCache.size,
664
+ dataDir: this.config.dataDir,
665
+ };
666
+ }
667
+ }
668
+
669
+ export { ContentMetadata, ConsensusProof };
670
+ export default ContentStore;
package/discord.md CHANGED
@@ -52,12 +52,14 @@ const { slices } = encoder.encode('Hello mesh!');
52
52
 
53
53
  ---
54
54
 
55
- ## πŸ“¦ Current Version: `1.2.0`
55
+ ## πŸ“¦ Current Version: `1.3.2`
56
56
 
57
- βœ… TME (Temporal Matrix Encoding)
57
+ βœ… TMEβ„’ (Temporal Matrix Encoding)
58
58
  βœ… ML-DSA-65 Post-Quantum Signatures
59
- βœ… Full security hardening suite
60
- βœ… 42+ tests passing
59
+ βœ… Code Proof Protocol (codebase verification)
60
+ βœ… Public Content Delivery API (v1.3.2 - gossip integration)
61
+ βœ… ECHOβ„’, PULSEβ„’, PHANTOMβ„’, BEACONβ„’ protocols
62
+ βœ… 68+ tests passing
61
63
 
62
64
  ---
63
65
 
@@ -65,6 +67,9 @@ const { slices } = encoder.encode('Hello mesh!');
65
67
  🌐 Website: https://yakmesh.dev
66
68
  πŸ“¦ npm: https://npmjs.com/package/yakmesh
67
69
  πŸ“– GitHub: https://github.com/yakmesh/yakmesh
70
+ πŸ’¬ Discord: https://discord.gg/E62tAE2wGh
71
+ πŸ“± Telegram: https://t.me/yakmesh
72
+ 𝕏 Twitter: https://x.com/yakmesh
68
73
  πŸ“„ Whitepaper: `docs/WHITEPAPER.md`
69
74
 
70
75
  **USPTO Serial No. 99594620**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yakmesh",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "description": "YAKMESH: Yielding Atomic Kernel Modular Encryption Secured Hub - Post-quantum secure P2P mesh network for the 2026 threat landscape",
5
5
  "type": "module",
6
6
  "main": "server/index.js",
package/server/index.js CHANGED
@@ -19,6 +19,9 @@ import { MeshNetwork } from '../mesh/network.js';
19
19
  import { ReplicationEngine } from '../database/replication.js';
20
20
  import { GossipProtocol } from '../gossip/protocol.js';
21
21
 
22
+ // Content store for public delivery
23
+ import { ContentStore, createContentAPI } from '../content/index.js';
24
+
22
25
  // Oracle system imports
23
26
  import {
24
27
  getOracle,
@@ -115,6 +118,9 @@ export class YakmeshNode {
115
118
  this.codeProof = null;
116
119
  this.consensus = null;
117
120
 
121
+ // Content store for public delivery
122
+ this.contentStore = null;
123
+
118
124
  // Time source detector
119
125
  this.timeSource = null;
120
126
 
@@ -206,12 +212,26 @@ export class YakmeshNode {
206
212
  if (topic === 'network_handshake') {
207
213
  this._handleNetworkHandshake(data, origin);
208
214
  }
215
+
216
+ // Handle content gossip (for public content delivery)
217
+ if (topic === 'content') {
218
+ if (this.contentStore) {
219
+ this.contentStore._handleContentGossip(data, origin);
220
+ }
221
+ }
209
222
  });
210
223
 
211
- // 5. Start HTTP server
224
+ // 5. Initialize content store for public delivery
225
+ this.contentStore = new ContentStore({
226
+ dataDir: this.config.database?.contentPath || './data/content',
227
+ quorumSize: 2,
228
+ });
229
+ await this.contentStore.init(this);
230
+
231
+ // 6. Start HTTP server
212
232
  await this._startHttpServer();
213
233
 
214
- // 6. Connect to bootstrap nodes
234
+ // 7. Connect to bootstrap nodes
215
235
  await this._connectToBootstrap();
216
236
 
217
237
  // 7. Initialize PeerQuanta integration (if enabled)
@@ -222,13 +242,18 @@ export class YakmeshNode {
222
242
  console.log('\nβœ“ Yakmesh Node is running!\n');
223
243
  console.log(` Node ID: ${this.identity.identity.nodeId}`);
224
244
  console.log(` HTTP: http://localhost:${this.config.network.httpPort}`);
245
+ console.log(` Content: http://localhost:${this.config.network.httpPort}/content`);
225
246
  console.log(` Dashboard: http://localhost:${this.config.network.httpPort}/dashboard`);
226
247
  console.log(` WebSocket: ws://localhost:${this.config.network.wsPort}`);
227
248
  console.log(` Algorithm: ML-DSA-65 (Post-Quantum)`);
228
249
  console.log(` Oracle: βœ“ ${this.oracle.selfHash.slice(0, 16)}...`);
229
250
  console.log(` Network: ${this.genesisNetwork.networkName} (${this.genesisNetwork.networkId})`);
251
+ if (this.contentStore) {
252
+ const stats = this.contentStore.getStats();
253
+ console.log(` Content: ${stats.totalObjects} objects (${stats.verified} verified)`);
254
+ }
230
255
  if (this.adapter) {
231
- console.log(` Adapter: βœ“ Enabled`);
256
+ console.log(` Adapter: βœ“ Enabled`);
232
257
  }
233
258
  console.log('');
234
259
 
@@ -491,6 +516,18 @@ export class YakmeshNode {
491
516
  return obj && typeof obj === 'object' && !Array.isArray(obj);
492
517
  };
493
518
 
519
+ // =========================================
520
+ // PUBLIC CONTENT API (No Auth for reads)
521
+ // =========================================
522
+
523
+ // Mount content API at /content
524
+ const contentAPI = createContentAPI(this.contentStore, {
525
+ writeLimiter,
526
+ readLimiter: generalLimiter,
527
+ validateString,
528
+ });
529
+ app.use('/content', contentAPI);
530
+
494
531
  // Serve dashboard
495
532
  app.get('/dashboard', (req, res) => {
496
533
  res.sendFile('dashboard/index.html', { root: import.meta.dirname + '/..' });
@@ -0,0 +1,8 @@
1
+ // Alpha node - connects to Gamma on LAN
2
+ export default {
3
+ nodeId: 'alpha-lan-test',
4
+ server: { port: 3001, host: '0.0.0.0' },
5
+ mesh: { port: 9002, host: '0.0.0.0' },
6
+ peers: ['ws://192.168.1.178:9001'],
7
+ dataDir: './test-nodes/data-alpha'
8
+ };