feat(modules): deepen trust, experimental, routing, and search
Hyper-P2P Module Tests / unit-all (push) Failing after 1h17m5s

Session revoke and encrypted-topic JSON helpers; silence/void channel
snapshots; relay tunnel route listing; grow-only set algebra; CPU top
donors; attestation chain slice/export; fulltext bulk and top terms.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Raven Scott
2026-05-21 02:25:10 -04:00
co-authored by Cursor
parent ed7f0ab34f
commit cce695e34a
12 changed files with 239 additions and 1 deletions
@@ -66,6 +66,25 @@ class HyperP2PSilenceProtocol extends EventEmitter {
return { absent: [...this._absent.values()] } return { absent: [...this._absent.values()] }
} }
listAbsent () {
return [...this._absent.values()]
}
absentCount () {
return this._absent.size
}
clear () {
const n = this._seen.size + this._absent.size
this._seen.clear()
this._absent.clear()
return n
}
revokeAbsent (id) {
return this._absent.delete(String(id))
}
async ready () { async ready () {
if (this.swarm || !this.topic) return this if (this.swarm || !this.topic) return this
await initModuleSwarm(this, { await initModuleSwarm(this, {
@@ -29,6 +29,19 @@ test('silence-protocol: merge', async (t) => {
await a.close() await a.close()
await b.close() await b.close()
}) })
test('silence-protocol: listAbsent clear revoke', async (t) => {
const s = new HyperP2PSilenceProtocol()
s.assertAbsent('a')
s.assertAbsent('b')
t.is(s.absentCount(), 2)
t.is(s.listAbsent().length, 2)
t.ok(s.revokeAbsent('a'))
t.is(s.absentCount(), 1)
s.clear()
t.is(s.absentCount(), 0)
await s.close()
})
test('hyper-p2p-silence-protocol: close without leak', async (t) => { test('hyper-p2p-silence-protocol: close without leak', async (t) => {
const m = new HyperP2PSilenceProtocol() const m = new HyperP2PSilenceProtocol()
await m.close() await m.close()
@@ -40,6 +40,32 @@ class HyperP2PVoidChannel extends EventEmitter {
return [...new Set([...this._subs.keys(), ...this._voids.keys()])] return [...new Set([...this._subs.keys(), ...this._voids.keys()])]
} }
clearVoid (channel) {
return this._voids.delete(String(channel))
}
subscriberCount () {
return this._subs.size
}
mergeSnapshot (snapshot) {
if (!snapshot || !Array.isArray(snapshot.voids)) return 0
let n = 0
for (const msg of snapshot.voids) {
if (!msg || !msg.channel) continue
const cur = this._voids.get(msg.channel)
if (!cur || msg.at > cur.at) {
this._voids.set(msg.channel, { ...msg, voided: true })
n++
}
}
return n
}
toSnapshot () {
return { voids: [...this._voids.values()] }
}
subscribeVoid (channel, fn) { subscribeVoid (channel, fn) {
assertNonEmpty(channel, 'channel') assertNonEmpty(channel, 'channel')
if (typeof fn !== 'function') throw new Error('fn must be a function') if (typeof fn !== 'function') throw new Error('fn must be a function')
@@ -114,6 +114,29 @@ class HyperP2PFulltextLite extends EventEmitter {
return [...this._docs.values()] return [...this._docs.values()]
} }
bulkAdd (entries) {
if (!Array.isArray(entries)) throw new Error('entries array required')
let n = 0
for (const e of entries) {
if (e && e.id != null) {
this.addDocument(e.id, e.text || '')
n++
}
}
return n
}
termCount () {
return this._index.size
}
topTerms (limit = 10) {
return [...this._index.entries()]
.map(([term, set]) => ({ term, docs: set.size }))
.sort((a, b) => b.docs - a.docs)
.slice(0, limit)
}
getStats () { getStats () {
return { return {
...this._stats, ...this._stats,
@@ -72,6 +72,27 @@ class HyperP2PRelayTunnel extends EventEmitter {
return true return true
} }
unpublish (rendezvousKey = null) {
const key = rendezvousKey || this.rendezvous
const had = this._routes.delete(key)
if (had && this._peerMsgs) gossipSend(this, { type: 'unpublish', rendezvous: key })
return had
}
listRoutes () {
return [...this._routes.values()]
}
getTunnel (tunnelId) {
return this._pending.get(tunnelId) || null
}
listTunnels (state = null) {
const all = [...this._pending.values()]
if (!state) return all
return all.filter((t) => t.state === state)
}
async ready () { async ready () {
if (this.swarm || !this.topic) return this if (this.swarm || !this.topic) return this
await initModuleSwarm(this, { await initModuleSwarm(this, {
@@ -55,6 +55,24 @@ class HyperP2PCrdtGrowOnlySet extends EventEmitter {
return n return n
} }
union (other) {
if (!other || typeof other.values !== 'function') throw new Error('other set required')
return this.merge(other.values())
}
isSubsetOf (other) {
if (!other || typeof other.has !== 'function') throw new Error('other set required')
for (const el of this._added) {
if (!other.has(el)) return false
}
return true
}
difference (other) {
if (!other || typeof other.has !== 'function') throw new Error('other set required')
return this.values().filter((el) => !other.has(el))
}
_gossip (data) { _gossip (data) {
if (!this._peerMsgs) return if (!this._peerMsgs) return
gossipSend(this, data) gossipSend(this, data)
@@ -62,6 +62,20 @@ class HyperP2PCpuShare extends EventEmitter {
return this.balance(peerId) >= cpuMs return this.balance(peerId) >= cpuMs
} }
topDonors (n = 5) {
return [...this._balances.entries()]
.sort((a, b) => b[1] - a[1])
.slice(0, n)
.map(([peerId, balance]) => ({ peerId, balance }))
}
resetBalance (peerId) {
const id = normalizePeerId(peerId)
const had = this._balances.delete(id)
if (had) this.emit('reset', { peerId: id })
return had
}
async ready () { async ready () {
if (this.swarm || !this.topic) return this if (this.swarm || !this.topic) return this
await initModuleSwarm(this, { await initModuleSwarm(this, {
@@ -59,6 +59,29 @@ class HyperP2PAttestationChain extends EventEmitter {
chainLength () { return this._chain.length } chainLength () { return this._chain.length }
at (index) {
if (index < 0 || index >= this._chain.length) return null
return { ...this._chain[index] }
}
slice (from = 0, to = this._chain.length) {
return this._chain.slice(from, to).map((l) => ({ ...l }))
}
payloads () {
return this._chain.map((l) => l.payload)
}
toJSON () {
return { chain: this.slice() }
}
fromJSON (snap) {
if (!snap || !Array.isArray(snap.chain)) throw new Error('invalid snapshot')
this._chain = snap.chain.map((l) => ({ ...l }))
return this._chain.length
}
async ready () { async ready () {
if (this.swarm || !this.topic) return this if (this.swarm || !this.topic) return this
await initModuleSwarm(this, { await initModuleSwarm(this, {
@@ -61,6 +61,38 @@ class HyperP2PEncryptedTopic extends EventEmitter {
return plain return plain
} }
hasTopic (topicId) {
return this._topics.has(String(topicId))
}
listTopics () {
return [...this._topics.keys()]
}
unregisterTopic (topicId) {
assertNonEmpty(topicId, 'topicId')
const had = this._topics.delete(topicId)
if (had) this.emit('unregistered', { topicId })
return had
}
rotateHint (topicId, newHint) {
assertNonEmpty(topicId, 'topicId')
assertNonEmpty(newHint, 'newHint')
if (!this._topics.has(topicId)) throw new Error(`topic not registered: ${topicId}`)
return this.registerTopic(topicId, newHint)
}
encryptJSON (topicId, obj) {
const json = b4a.from(JSON.stringify(obj))
return this.encryptPayload(topicId, json)
}
decryptJSON (topicId, buf) {
const plain = this.decryptPayload(topicId, buf)
return JSON.parse(b4a.toString(plain))
}
_gossip (data) { _gossip (data) {
if (!this._peerMsgs) return if (!this._peerMsgs) return
gossipSend(this, data) gossipSend(this, data)
@@ -37,3 +37,15 @@ test('getStats', async (t) => {
t.is(m.getStats().encrypted, 1) t.is(m.getStats().encrypted, 1)
await m.close() await m.close()
}) })
test('json list unregister', async (t) => {
const m = new HyperP2PEncryptedTopic()
m.registerTopic('t', 'hint')
const buf = m.encryptJSON('t', { ok: 1 })
t.is(m.decryptJSON('t', buf).ok, 1)
t.ok(m.hasTopic('t'))
t.alike(m.listTopics(), ['t'])
t.ok(m.unregisterTopic('t'))
t.not(m.hasTopic('t'))
await m.close()
})
@@ -42,6 +42,26 @@ class HyperP2PSessionRotation extends EventEmitter {
return [...this._sessions.values()] return [...this._sessions.values()]
} }
revokeSession (sessionId) {
assertNonEmpty(sessionId, 'sessionId')
const had = this._sessions.delete(sessionId)
if (had) {
this._gossip({ type: 'session-revoke', sessionId, at: Date.now() })
this.emit('revoked', { sessionId })
}
return had
}
validateToken (sessionId, token) {
const s = this.getSession(sessionId)
return !!(s && s.token === token)
}
latestVersion (sessionId) {
const s = this.getSession(sessionId)
return s ? s.version : 0
}
_gossip (data) { _gossip (data) {
if (!this._peerMsgs) return if (!this._peerMsgs) return
gossipSend(this, data) gossipSend(this, data)
@@ -49,7 +69,13 @@ class HyperP2PSessionRotation extends EventEmitter {
} }
_onGossip (d) { _onGossip (d) {
if (!d || d.type !== 'session-rotate' || !d.sessionId) return if (!d || !d.sessionId) return
if (d.type === 'session-revoke') {
this._stats.gossipIn++
this._sessions.delete(d.sessionId)
return
}
if (d.type !== 'session-rotate') return
this._stats.gossipIn++ this._stats.gossipIn++
const prev = this._sessions.get(d.sessionId) const prev = this._sessions.get(d.sessionId)
if (!prev || d.version >= prev.version) { if (!prev || d.version >= prev.version) {
@@ -36,3 +36,14 @@ test('getStats', async (t) => {
t.is(m.getStats().rotations, 1) t.is(m.getStats().rotations, 1)
await m.close() await m.close()
}) })
test('validateToken revokeSession', async (t) => {
const m = new HyperP2PSessionRotation()
m.rotateSession('s', 'secret')
t.ok(m.validateToken('s', 'secret'))
t.not(m.validateToken('s', 'wrong'))
t.is(m.latestVersion('s'), 1)
t.ok(m.revokeSession('s'))
t.is(m.getSession('s'), null)
await m.close()
})