OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-proto-peer / .probe-delete-audit.mjs

61 lines · javascript ref:
// READ-ONLY forensics: replicate each DID's firehose with a KEYLESS reader (own throwaway
// corestore, no writes, daemon untouched) and audit every #commit op. The signature we are
// hunting is delete/create OSCILLATION on the same path — a lagging co-owner deleting a record
// the caught-up co-owner then re-creates. A genuine user delete is terminal.
import './lib/bare-globals.js'
import fs from 'bare-fs'
import b4a from 'b4a'
import Hyperswarm from 'hyperswarm'
import { secp256k1 } from '@noble/curves/secp256k1'
import { DidPeerRouter } from 'atproto-did-peer'
import { openCorestore } from 'atproto-store/lib/corestore.js'
import { decodeCommitFrame } from 'atproto-store/lib/firehose.js'
import { discoveryTopic } from 'atproto-identity/lib/discovery.js'

const DIR = '/tmp/claude-1000/-storage-projects-code-hypercore-kb/6cf0ccb9-db69-4cc4-8094-9b7f3d26480b/scratchpad/delete-audit'
try { fs.rmSync(DIR, { recursive: true }) } catch {}
const cfg = JSON.parse(fs.readFileSync('/home/fleeky/.config/hyper-proto-peer/config.json', 'utf8'))
const store = openCorestore(DIR)
const swarm = new Hyperswarm()
const router = new DidPeerRouter(store, { swarm })
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))

for (const d of (cfg.dids || [])) {
  const verifyKey = b4a.from(secp256k1.getPublicKey(b4a.from(d.signingKey, 'hex'), true))
  router.add(d.did, { verifyKey, noRepo: true, onError: () => {} })
  swarm.join(discoveryTopic(d.did), { client: true, server: true })
}
await swarm.flush().catch(() => {})

for (const d of (cfg.dids || [])) {
  const tag = (d.handle || d.did).padEnd(22)
  let front
  try { front = await router.front(d.did, { timeout: 45000 }) } catch (e) { console.log(tag, 'NO FRONT:', e.message); continue }
  try { await Promise.race([front.seq.core.update({ wait: true }), sleep(15000)]) } catch {}
  const total = front.seq && front.seq.core ? front.seq.core.length : 0
  const seen = []            // [{action, path}] in frame order
  let n = 0
  try {
    for await (const { bytes } of front.subscribeRepos(0)) {
      const dec = decodeCommitFrame(bytes)
      if (dec && dec.body && dec.body.ops) for (const o of dec.body.ops) seen.push({ action: o.action, path: o.path, rev: dec.body.rev })
      if (++n >= total) break
    }
  } catch (e) { console.log(tag, 'read stopped:', e.message) }

  const dels = seen.filter((o) => o.action === 'delete')
  // oscillation = a path that is CREATED after having been DELETED
  const osc = []
  const state = new Map()
  for (const o of seen) {
    if (o.action === 'delete') state.set(o.path, o.rev)
    else if (state.has(o.path)) { osc.push({ path: o.path, deletedAt: state.get(o.path), recreatedAt: o.rev }); state.delete(o.path) }
  }
  console.log('\n%s frames=%d ops=%d deletes=%d', tag, n, seen.length, dels.length)
  for (const o of dels) console.log('    delete  %s  @rev %s', o.path, o.rev)
  console.log('    OSCILLATIONS (delete->recreate, the spurious-delete signature):', osc.length)
  for (const o of osc) console.log('      !! %s deleted@%s recreated@%s', o.path, o.deletedAt, o.recreatedAt)
}
try { await swarm.destroy() } catch {}
try { await store.close() } catch {}