OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / test / tracker-rebind-e2e.test.js

238 lines · javascript ref:
// E2E proof for strict-bootstrap tracker semantics on mirrors.
//
// History: this test originally tried to validate a runtime "rebind"
// (close+reopen the autobase against a different root in the same
// corestore namespace). That approach crashes inside autobase's apply
// path because corestore's auto-derived local writer carries blocks
// from the old root and can't be reapplied under the new one. See
// stortron's 2026-05-05 outage for the production reproduction.
//
// The fix in 0.1.57 is the dodge: a mirror is constructed in
// `requireBootstrap` mode and refuses to lazy-open a tracker without
// a known bootstrap key. The sibling autobase that previously caused
// the divergence is never created in the first place. When the bundle
// or event log delivers the canonical key, the tracker opens cleanly
// against canonical from the very first call. No close+reopen, no
// orphan local-writer state, no apply crash.
//
// Phases:
//   1. Primary creates the canonical org tracker, writes one issue.
//   2. Strict-mode mirror (no bootstrap yet) calls getForOrg ⇒
//      throws ETRACKER_NOT_READY. listOrgs is empty (no sibling).
//   3. Bundle/event-log delivers canonical key ⇒ bindOrgBootstrapKey.
//   4. Mirror's getForOrg now succeeds at canonical root.
//   5. Mirror reads primary's issue (read replication).
//   6. Mirror restart with same storage ⇒ boots cleanly, tracker still
//      canonical, primary's issue still readable. This phase is the
//      regression guard for the production crash.

import test from 'brittle'
import { mkdtempSync } from '#node:fs'
import { join } from '#node:path'
import { tmpdir } from '#node:os'
import b4a from 'b4a'
import createTestnet from '@hyperswarm/testnet'

import { startPearGit } from '../index.js'
import { IssueRegistry } from '../lib/issue-registry.js'
import Corestore from 'corestore'

let n = 0
function tmpStorage () {
  return mkdtempSync(join(tmpdir(), 'pgv3-strict-' + (n++) + '-'))
}
function tmpSockPath () {
  return join(mkdtempSync(join(tmpdir(), 'pgv3-strict-sock-' + (n++) + '-')), 'svc.sock')
}

async function waitFor (predicate, timeoutMs = 8000) {
  const t0 = Date.now()
  while (Date.now() - t0 < timeoutMs) {
    try { if (await predicate()) return true } catch {}
    await new Promise((r) => setTimeout(r, 50))
  }
  return false
}

test('strict mode: getForOrg without bootstrap throws ETRACKER_NOT_READY (no sibling autobase)', async (t) => {
  // Pure unit test of the strict-mode contract: mirror IssueRegistry
  // refuses to mint a tracker when no bootstrap is available.
  const storage = tmpStorage()
  const corestore = new Corestore(join(storage, 'store'))
  await corestore.ready()
  t.teardown(async () => { try { await corestore.close() } catch {} })

  const registry = new IssueRegistry(corestore, {
    indexPath: join(storage, 'issue-registry-index.json'),
    requireBootstrap: true
  })

  let thrown = null
  try { await registry.getForOrg('demo-org') } catch (err) { thrown = err }
  t.ok(thrown, 'getForOrg without bootstrap throws')
  t.is(thrown.code, 'ETRACKER_NOT_READY', 'error.code is ETRACKER_NOT_READY')
  t.ok(/bootstrap key not yet known/.test(thrown.message), 'message names the missing input')

  // Critical: no sibling autobase was created.
  t.is(registry.listOrgs().length, 0, 'no tracker was opened (no sibling autobase)')
  t.is(Object.keys(registry._index.orgs).length, 0, 'index file did not record a sibling root')

  await registry.close()
})

test('strict mode: bind then getForOrg opens cleanly against canonical, survives restart', async (t) => {
  t.timeout(90000)
  const testnet = await createTestnet(3, t.teardown)
  const topic = b4a.alloc(32, 'pgv3-strict-bootstrap')

  // ── Phase 1: Primary, permissive mode ─────────────────────────
  const nodeA = await startPearGit({
    storage: tmpStorage(),
    uds: tmpSockPath(),
    http: { port: 0 },
    swarm: { topic, swarm: { bootstrap: testnet.bootstrap } },
    deploy: false,
    sync: false
  })
  t.teardown(async () => { try { await nodeA.close() } catch {} })

  const trackerA = await nodeA.issueRegistry.getForOrg('demo-org')
  await trackerA.create({ title: 'first canonical', author: 'alice' })
  const canonicalKey = trackerA.keyHex
  t.ok(canonicalKey, 'primary owns a canonical tracker')

  // ── Phase 2: Mirror in strict mode, no bootstrap delivered yet ─
  // Mirror is mounted with `origin` set ⇒ requireBootstrap=true. Bundle
  // is intentionally NOT carrying issueOrgs so the tracker key is
  // unknown at startup. This is the production scenario where the
  // OrgEventLog hasn't delivered the key yet.
  const mirrorStorage = tmpStorage()
  const nodeB1 = await startPearGit({
    storage: mirrorStorage,
    uds: tmpSockPath(),
    http: { port: 0 },
    swarm: { topic, swarm: { bootstrap: testnet.bootstrap } },
    deploy: false,
    sync: false,
    origin: { serverKey: 'a'.repeat(64) } // any non-null origin ⇒ requireBootstrap=true
  })
  // No teardown for nodeB1 — closed mid-test for the restart phase.

  let strictThrew = null
  try { await nodeB1.issueRegistry.getForOrg('demo-org') } catch (err) { strictThrew = err }
  t.ok(strictThrew, 'mirror getForOrg without bootstrap throws (no sibling created)')
  t.is(strictThrew && strictThrew.code, 'ETRACKER_NOT_READY', 'error.code surfaces to caller')
  t.is(nodeB1.issueRegistry.listOrgs().length, 0, 'no sibling tracker opened on mirror')

  // ── Phase 3: Canonical key arrives (simulating event-log delivery) ─
  const bound = nodeB1.issueRegistry.bindOrgBootstrapKey('demo-org', canonicalKey)
  t.ok(bound, 'bindOrgBootstrapKey accepted the canonical key')

  // ── Phase 4: First open succeeds at canonical root ────────────
  const trackerB1 = await nodeB1.issueRegistry.getForOrg('demo-org')
  t.is(trackerB1.keyHex, canonicalKey, 'mirror tracker opened at canonical root from the start')

  // ── Phase 5: Read replication ─────────────────────────────────
  const readReplicated = await waitFor(async () => {
    const issues = await trackerB1.list()
    return issues.some(i => i.title === 'first canonical')
  }, 8000)
  t.ok(readReplicated, "mirror reads primary's canonical issue")

  // ── Phase 6 (CRITICAL): Restart with same storage ─────────────
  // Boot picks up the bootstrap key from the persisted index (we just
  // recorded canonicalKey in phase 4). Strict-mode + bundle-or-index
  // delivery means tracker opens cleanly. Because no sibling was ever
  // created in phase 2, the corestore namespace has no orphan blocks
  // from a defunct root. Apply has nothing stale to choke on.
  await nodeB1.close()

  let nodeB2 = null
  let bootError = null
  try {
    nodeB2 = await startPearGit({
      storage: mirrorStorage, // SAME path
      uds: tmpSockPath(),
      http: { port: 0 },
      swarm: { topic, swarm: { bootstrap: testnet.bootstrap } },
      deploy: false,
      sync: false,
      origin: { serverKey: 'a'.repeat(64) }
    })
  } catch (err) {
    bootError = err
  }
  t.absent(bootError, 'mirror restart boots cleanly (no autobase apply crash)')

  if (nodeB2) {
    t.teardown(async () => { try { await nodeB2.close() } catch {} })

    const trackerB2 = await nodeB2.issueRegistry.getForOrg('demo-org')
    t.is(trackerB2.keyHex, canonicalKey, 'restart preserves canonical root binding')

    const stillReplicated = await waitFor(async () => {
      const issues = await trackerB2.list()
      return issues.some(i => i.title === 'first canonical')
    }, 8000)
    t.ok(stillReplicated, "post-restart mirror still reads primary's canonical issue")
  }
})

test('permissive mode: primary still auto-derives a tracker root when no bootstrap given', async (t) => {
  // Sanity check that strict mode is opt-in. A primary daemon (no
  // origin / no mirror config) keeps the legacy behavior: getForOrg
  // mints a fresh root if none is known. This is correct on a primary —
  // the primary OWNS canonical roots.
  const testnet = await createTestnet(3, t.teardown)
  const topic = b4a.alloc(32, 'pgv3-permissive')

  const node = await startPearGit({
    storage: tmpStorage(),
    uds: tmpSockPath(),
    http: { port: 0 },
    swarm: { topic, swarm: { bootstrap: testnet.bootstrap } },
    deploy: false,
    sync: false
  })
  t.teardown(async () => { try { await node.close() } catch {} })

  const tracker = await node.issueRegistry.getForOrg('first-org')
  t.ok(tracker.keyHex, 'primary auto-derived a fresh tracker root')
  await tracker.create({ title: 'works', author: 'alice' })
  t.is((await tracker.list()).length, 1, 'primary can write to its tracker')
})

test('strict mode: bindOrgBootstrapKey is a no-op when tracker is already open', async (t) => {
  // Documents the deliberate non-behavior: at runtime, bind never
  // close+reopens. If a tracker is already open at root A, binding
  // root B is a no-op. The next boot picks up root B from the
  // persisted index/bundle. This is the conservative choice that
  // avoids the corestore-state crash; recovery for an already-corrupt
  // mirror is `pgh device join` (re-pair), not an attempted runtime
  // rebind.
  const storage = tmpStorage()
  const corestore = new Corestore(join(storage, 'store'))
  await corestore.ready()
  t.teardown(async () => { try { await corestore.close() } catch {} })

  const registry = new IssueRegistry(corestore, {
    indexPath: join(storage, 'issue-registry-index.json'),
    requireBootstrap: false // permissive so we can open without a key
  })

  // Open with no bootstrap ⇒ auto-derived sibling (the legacy state).
  const tracker = await registry.getForOrg('legacy-org')
  const siblingRoot = tracker.keyHex
  t.ok(siblingRoot, 'tracker opened with auto-derived root')

  // Try to bind a different root.
  const fakeCanonical = 'a'.repeat(64)
  const result = registry.bindOrgBootstrapKey('legacy-org', fakeCanonical)
  t.absent(result, 'bind returns false when tracker already open (no-op)')

  const stillOpen = await registry.getForOrg('legacy-org')
  t.is(stillOpen.keyHex, siblingRoot, 'tracker stays on its original root at runtime')

  await registry.close()
})