OverviewFilesCommitsIssuesDocsReleasesPeers

p2pbuilders commits / 648a108

web: seed Hyperdrive content/blobs core (not just metadata) on publish
A Hyperdrive keeps file bytes in a separate content core; seeding only the drive key left relays with metadata but no files. publish.mjs now also seeds the blobs core key. Adds check-durable.mjs, a host-down read probe to verify real fleet durability. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
648a108a1eaa38445cde9ea022e69ee9578a665c
by bigdestiny2 <defidon@protonmail.com> · 2026-06-23T17:53:10.000Z

Changed files (2) +79 -5

web/check-durable.mjs +31 -0
……@@ -0,0 +1,31 @@
1#!/usr/bin/env node
2// check-durable.mjs — TRUE durability probe. With NO host of mine running, open
3// the drive from a fresh store and actually READ a file — which forces fetching
4// the content/blobs core from whatever peer serves it. If a relay serves the
5// file with all my hosts down, the drive is genuinely durable. Exit 0 = durable.
6import { HiveRelayClient } from '/Users/localllm/Projects/pear-ecosystem/00-core/hiverelay/packages/client/index.js'
7import { fileURLToPath } from 'url'
8import { dirname, join } from 'path'
9
10const __dir = dirname(fileURLToPath(import.meta.url))
11const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
12const KEY = process.argv[2] || 'ac1977a75cc84b46af0af8bb559cd4ebbe10507eb0f51d863e289d09635f6d74'
13const withTimeout = (p, ms) => Promise.race([p, new Promise((_, rej) => setTimeout(() => rej(new Error('timeout')), ms))])
14
15const client = new HiveRelayClient({ storage: join(__dir, '.hiverelay-probe') })
16await client.start()
17await sleep(4000)
18
19let fileOk = false, fileLen = 0, durable = null
20try {
21 const drive = await client.open(KEY, { wait: true, timeout: 30000 })
22 await drive.ready()
23 try { const buf = await withTimeout(drive.get('/index.html'), 60000); fileOk = !!(buf && buf.length); fileLen = buf ? buf.length : 0 } catch {}
24 if (typeof client.waitForDurable === 'function') {
25 try { durable = await client.waitForDurable(drive.key, { timeoutMs: 40000, minPeers: 1 }) } catch {}
26 }
27} catch (e) { console.log('[probe] open error:', e.message) }
28
29console.log('PROBE ' + JSON.stringify({ key: KEY, fileOk, fileLen, durable }))
30try { if (client.destroy) await client.destroy() } catch {}
31process.exit(fileOk ? 0 : 2)
web/publish.mjs +48 -5
……@@ -36,12 +36,25 @@ async function main () {
3636 await sleep(LOCAL ? 1500 : 5000)
3737 console.log('[p2pb] relays connected:', relayCount(client.getStatus && client.getStatus()))
3838
39 const DURABILITY = process.env.DURABILITY || 'archive' // AutoHeal: ≥7 replicas across ≥4 regions, independent of this process
3940 const files = SITE_FILES.map((p) => ({ path: '/' + p, content: readFileSync(join(__dir, p)) }))
4041 console.log('[p2pb] publishing site drive (' + files.length + ' files)…')
41 const drive = await client.publish(files, { appId: 'p2pbuilders-web', seed: !LOCAL, replicas: 4, ttlDays: 365 })
42 const drive = await client.publish(files, { appId: 'p2pbuilders-web', seed: !LOCAL, replicas: 4, ttlDays: 365, durability: DURABILITY })
4243 const driveKey = drive.key.toString('hex')
4344 console.log('[p2pb] site drive key:', driveKey)
4445
46 // A Hyperdrive stores file bytes in a SEPARATE content/blobs core. seed(driveKey)
47 // only advertises the metadata core, so relays hold metadata but not the files —
48 // and the drive can't be reconstructed once the publisher leaves. Seed the blobs
49 // core too so the fleet durably holds the actual content.
50 let contentKey = null
51 try {
52 await drive.ready()
53 const blobs = drive.blobs || (typeof drive.getBlobs === 'function' ? await drive.getBlobs() : null)
54 if (blobs && blobs.core) { await blobs.core.ready(); contentKey = blobs.core.key.toString('hex') }
55 } catch (e) { console.log('[p2pb] blobs-core note:', e.message) }
56 console.log('[p2pb] content/blobs core key:', contentKey || '(none — empty drive?)')
57
4558 if (LOCAL) {
4659 console.log('\n[p2pb] ── LOCAL TEST (not seeded to relays, not in catalog) ──')
4760 console.log('[p2pb] Open this in PearBrowser:\n\n hyper://' + driveKey + '/\n')
……@@ -60,13 +73,43 @@ async function main () {
6073 await client.publish([{ path: '/manifest.json', content: JSON.stringify(manifest, null, 2) }],
6174 { appId: 'p2pbuilders-web-manifest', seed: true, replicas: 4, ttlDays: 365 })
6275 try {
63 const res = await client.seed(Buffer.from(driveKey, 'hex'), { replicas: 4, ttlDays: 365, timeout: 30000 })
64 console.log('[p2pb] seed acceptances:', (res || []).length)
76 const res = await client.seed(Buffer.from(driveKey, 'hex'), { replicas: 4, ttlDays: 365, timeout: 30000, durability: DURABILITY })
77 console.log('[p2pb] metadata seed acceptances:', (res || []).length)
6578 } catch (err) { console.log('[p2pb] seed note:', err.message) }
79 if (contentKey) {
80 try {
81 const res = await client.seed(Buffer.from(contentKey, 'hex'), { replicas: 4, ttlDays: 365, timeout: 30000, durability: DURABILITY })
82 console.log('[p2pb] content seed acceptances:', (res || []).length)
83 } catch (err) { console.log('[p2pb] content seed note:', err.message) }
84 }
85
86 // Wait for PROOF a relay actually replicated the bytes — so durability doesn't
87 // depend on this process staying alive (archive-tier AutoHeal maintains it).
88 if (typeof client.waitForDurable === 'function') {
89 console.log('[p2pb] waiting for relay byte-replication evidence…')
90 try {
91 const durable = await client.waitForDurable(drive.key, { timeoutMs: 120000, minPeers: 1 })
92 console.log('[p2pb] durable status:', JSON.stringify(durable))
93 if (!durable || !durable.durable) console.warn('[p2pb] WARNING: no relay proved full replication yet; rerun or keep alive longer.')
94 } catch (err) { console.warn('[p2pb] durability check note:', err.message) }
95 }
6696
6797 console.log('\n[p2pb] Live at: hyper://' + driveKey + '/\n')
68 if (process.env.KEEP === '1') { console.log('[p2pb] staying alive so relays anchor (Ctrl-C to stop)…'); setInterval(() => {}, 1 << 30); return }
69 await sleep(20000)
98 if (process.env.KEEP === '1') {
99 console.log('[p2pb] staying alive; polling relay durability until a relay proves it caught up…')
100 let confirmed = false
101 for (;;) {
102 if (typeof client.waitForDurable === 'function') {
103 try {
104 const d = await client.waitForDurable(drive.key, { timeoutMs: 25000, minPeers: 1 })
105 if (d && d.durable) { if (!confirmed) console.log('[p2pb] DURABLE ✓ ' + JSON.stringify(d)); confirmed = true }
106 else console.log('[p2pb] not-yet-durable ' + JSON.stringify(d))
107 } catch (e) { console.log('[p2pb] durability-check-error ' + e.message) }
108 }
109 await sleep(30000)
110 }
111 }
112 await sleep(8000)
70113 try { if (client.destroy) await client.destroy() } catch {}
71114 process.exit(0)
72115}