OverviewFilesCommitsIssuesDocsReleasesPeers

favela-peer / test / knock.test.js

123 lines · javascript ref:
// The doorbell.
//
// A stranger holds nothing of mine and I hold nothing of theirs, so neither replication nor a
// follow can carry a first contact. keet answers this with an optimistic append that lands as a
// REQUEST a member must accept; this is the same shape.
//
// ⛔ The thing that must not happen: accepting a knock does NOT make the knocker a writer on my
// feed. A knock carries their FEED KEY, and accepting means I follow THEM. Nothing ever grants
// write access to my feed except my own devices — that is the invariant the whole model rests on,
// and a doorbell is exactly where it would be easiest to lose.
//
// A knock is therefore the ONE op accepted from an unadmitted device, it can only write its own
// entry in a reserved namespace, and it grants nothing on arrival.

import test from 'brittle'
import { proofsFor, identity } from './helpers.js'
import { applyOp, isRosterKey, ROSTER, KNOCKS } from '../lib/feed.js'

const kv = () => {
  const m = new Map()
  return {
    m,
    async set (k, v) { m.set(k, v) },
    async get (k) { return m.has(k) ? m.get(k) : null },
    entries () { return [...m.entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)) }
  }
}

const D1 = 'd1'.repeat(32)
const STRANGER = 'ff'.repeat(32)
const THEIR_FEED = 'ab'.repeat(32)

const run = async (ops, view = kv()) => {
  for (const [device, op] of ops) {
    const genesis = !view.entries().some(([k]) => isRosterKey(k))
    await applyOp(view, op, { device, genesis })
  }
  return view
}

// One owner's devices. Proofs are minted once, so every call site stays synchronous.
const PROOF = await proofsFor([D1, THEIR_FEED])
// ⛔ A SEPARATE identity — a stranger is someone else's device, not a co-owner. Minting it
// from the same root would make every "a stranger cannot" test pass for the wrong reason.
const OUTSIDER_ID = await identity()
const OUTSIDER = await OUTSIDER_ID.device()
const OUTSIDER_ROOT = OUTSIDER_ID.rootHex
const STRANGER_PROOF = OUTSIDER.bind(STRANGER)
const addDevice = (device) => ({ t: 'add-device', device, proof: device === STRANGER ? STRANGER_PROOF : PROOF[device] })
// ⛔ A knock carries a proof binding the KNOCKING writer core to its identity root (feed.js), and is
// keyed by that root. The stranger's proof chains to OUTSIDER's root; the owner's to PROOF.__root.
const knock = (feed, writer = STRANGER) => ({ t: 'knock', feed, proof: writer === STRANGER ? STRANGER_PROOF : PROOF[writer] })
const put = (collection, name) => ({ t: 'put', collection, name, $oid: 'o', size: 1 })

test('a stranger may knock, and it is recorded', async (t) => {
  const v = await run([[D1, addDevice(D1)], [STRANGER, knock(THEIR_FEED)]])
  t.alike(await v.get(KNOCKS + '/' + OUTSIDER_ROOT), { device: STRANGER, feed: THEIR_FEED },
    'keyed by the identity that proved it rang, naming the feed it asked about')
})

test('⛔ knocking grants NOTHING — not a writer, not a device', async (t) => {
  const v = await run([
    [D1, addDevice(D1)],
    [STRANGER, knock(THEIR_FEED)],
    [STRANGER, put('post', 'x')],
    [STRANGER, addDevice(STRANGER)]
  ])
  t.is(await v.get('post/x'), null, 'a knocker still cannot write a record')
  t.is(await v.get(ROSTER + '/' + STRANGER), null, 'nor admit itself')
  t.ok(await v.get(KNOCKS + '/' + OUTSIDER_ROOT), 'only the knock landed')
})

test('⛔ a knock cannot forge another feed\'s entry or reach outside the namespace', async (t) => {
  const v = await run([
    [D1, addDevice(D1)],
    // A valid proof on each, so these test the FEED validation, not a missing proof.
    [STRANGER, { t: 'knock', proof: STRANGER_PROOF }],
    [STRANGER, { t: 'knock', feed: 'not/a/segment', proof: STRANGER_PROOF }],
    [STRANGER, { t: 'knock', feed: '', proof: STRANGER_PROOF }]
  ])
  t.alike(v.entries().filter(([k]) => k.startsWith(KNOCKS)), [], 'malformed knocks write nothing')
})

test('knocking twice is one knock', async (t) => {
  const v = await run([[D1, addDevice(D1)], [STRANGER, knock(THEIR_FEED)], [STRANGER, knock(THEIR_FEED)]])
  t.is(v.entries().filter(([k]) => k.startsWith(KNOCKS)).length, 1,
    'the entry is keyed by the identity root, so a flood is one row')
})

test('an admitted device may knock too — it is not an unadmitted-only op', async (t) => {
  const v = await run([[D1, addDevice(D1)], [D1, knock(THEIR_FEED, D1)]])
  t.alike(await v.get(KNOCKS + '/' + PROOF.__root), { device: D1, feed: THEIR_FEED })
})

test('⛔ a knock is not a record — it never appears in the record set', async (t) => {
  const { RecordLog } = await import('../lib/log.js')
  const { RecordStore } = await import('../lib/record-store.js')
  const fs = await import('#node:fs')
  const os = await import('#node:os')
  const { join } = await import('#node:path')

  const v = await run([[D1, addDevice(D1)], [STRANGER, knock(THEIR_FEED)]])
  const kvAdapter = {
    set: v.set, get: v.get, del: async () => {},
    list: async (prefix = '') => Object.fromEntries(v.entries().filter(([k]) => k.startsWith(prefix)))
  }
  const log = new RecordLog(kvAdapter, new RecordStore(fs.mkdtempSync(join(os.tmpdir(), 'k-'))))
  t.alike(await log.list(), [], 'reserved namespaces never resolve as records')
})

test('dismissing a knock requires admission', async (t) => {
  const v = await run([
    [D1, addDevice(D1)],
    [STRANGER, knock(THEIR_FEED)],
    [STRANGER, { t: 'dismiss-knock', root: OUTSIDER_ROOT }]
  ])
  t.alike(await v.get(KNOCKS + '/' + OUTSIDER_ROOT), { device: STRANGER, feed: THEIR_FEED },
    'a stranger cannot clear its own knock — nor anyone else\'s')

  await applyOp(v, { t: 'dismiss-knock', root: OUTSIDER_ROOT }, { device: D1, genesis: false })
  t.alike(await v.get(KNOCKS + '/' + OUTSIDER_ROOT), { $deleted: true }, 'the owner can')
})