OverviewFilesCommitsIssuesDocsReleasesPeers

favela-android / test / repair-store-backend.test.mjs

90 lines · javascript ref:
// Repairing an identity minted onto the LEGACY record backend.
//
// favela-android 0.1.6 minted did:plc:zl5loetx46p6xusgc47h3mia without `storeBackend`. bootSwarmDevice
// defaults that to null and only `storeBackend === 'overlay'` builds the OverlayRepoStore, so the DID
// came up on the legacy inline substrate: it authored commits, and the appview refused to materialise
// its post bytes ("refusing to CREATE bytes for a roster DID"). Posting was silently broken.
//
// The identity itself is fine — a real did:plc, permanently registered, with a sealed recovery vault.
// Wiping app storage to change one config field would destroy all three to fix none of them, and on a
// release-signed app the config is app-private, so there is no way to edit it from outside either. The
// repair has to be in the app.
//
// ⛔ WHY THIS IS SAFE, AND WHERE THE DANGER ACTUALLY IS. Flipping a populated legacy DID to 'overlay' is
// the e2jev hazard (hyper-social#16 P1): under PointerSubstrate its inline records read as an EMPTY set,
// and the first sign() would publish a delete-every-record commit. What makes the flip safe is that
// hyper-proto-peer already runs migrateInline at boot for exactly this — marker-guarded
// (__inline_migrated__), idempotent, content-preserving, and it REFUSES any key whose oid would change.
// Its own suite proves the dangerous arm: "sign() REFUSED — no delete-every-record commit was published",
// then "after migration the set reads NON-empty" (5/5, 28/28).
//
// So this file is not the migration. It is the narrow decision of WHICH entries may be flipped into it.
//
// ⛔ THE DISCRIMINATOR IS forceCreator. mint writes forceCreator:true and no `joiner`; coownerJoin writes
// joiner:true and DELETES forceCreator. So "this node minted it" is expressible, and a co-owned identity
// this phone merely joined is never touched — its backend is the origin's decision, not ours, and
// migrating someone else's production repo from a handset is not a thing this should be able to do.
//
// Run: bare test/repair-store-backend.test.mjs
import { repairStoreBackend } from '../lib/repair-store-backend.js'

let pass = 0
const ok = (c, m) => { if (!c) throw new Error('FAIL: ' + m); console.log('  ok -', m); pass++ }

const minted = (did) => ({ did, deviceMnemonic: 'x', signingKey: 'y', members: ['aa'], forceCreator: true })
const joined = (did) => ({ did, deviceMnemonic: 'x', signingKey: 'y', members: ['aa'], joiner: true })

// ── the case this exists for ─────────────────────────────────────────────────────────────────────
{
  const cfg = { dids: [minted('did:plc:zl5loetx46p6xusgc47h3mia')] }
  const r = repairStoreBackend(cfg)
  ok(cfg.dids[0].storeBackend === 'overlay', 'a minted entry with no backend is flipped to overlay')
  ok(r.changed.length === 1 && r.changed[0] === 'did:plc:zl5loetx46p6xusgc47h3mia', 'and reported by DID')
}

// ── everything it must NOT touch ─────────────────────────────────────────────────────────────────
{
  // Already correct — nothing to do, and reporting a change would make the caller write the file for no
  // reason on every single boot.
  const cfg = { dids: [{ ...minted('did:plc:a'), storeBackend: 'overlay' }] }
  ok(repairStoreBackend(cfg).changed.length === 0, 'an entry already on overlay is left alone')
}
{
  // A DELIBERATE legacy choice is still a choice. Only an ABSENT field means "never decided".
  const cfg = { dids: [{ ...minted('did:plc:b'), storeBackend: 'legacy' }] }
  repairStoreBackend(cfg)
  ok(cfg.dids[0].storeBackend === 'legacy', 'an explicit non-overlay backend is not overridden')
}
{
  // ⛔ THE ONE THAT MATTERS. A joined identity is co-owned: its records may already be pointer-shaped on
  // the origin, and its backend is the origin's call. A handset must not migrate someone else's repo.
  const cfg = { dids: [joined('did:plc:wj62ebbusln6gxo5azo6')] }
  const r = repairStoreBackend(cfg)
  ok(cfg.dids[0].storeBackend === undefined, 'a JOINED identity is never flipped')
  ok(r.changed.length === 0, 'and never reported as changed')
}
{
  // Belt and braces: both flags set is an incoherent entry we did not write. Refuse rather than guess.
  const cfg = { dids: [{ ...minted('did:plc:c'), joiner: true }] }
  repairStoreBackend(cfg)
  ok(cfg.dids[0].storeBackend === undefined, 'an entry claiming BOTH roles is left alone')
}

// ── shape robustness ─────────────────────────────────────────────────────────────────────────────
// This runs at boot, before the node exists. It must never be the reason a phone fails to start.
ok(repairStoreBackend(null).changed.length === 0, 'a null config is not a crash')
ok(repairStoreBackend({}).changed.length === 0, 'a config with no dids is not a crash')
ok(repairStoreBackend({ dids: 'nonsense' }).changed.length === 0, 'a malformed dids is not a crash')
ok(repairStoreBackend({ dids: [null, minted('did:plc:d')] }).changed.length === 1, 'a null entry is skipped, not fatal')

// ── idempotent ───────────────────────────────────────────────────────────────────────────────────
// The caller persists only when something changed, so a second boot must report nothing — otherwise
// every boot rewrites the config file of a device whose storage we care about.
{
  const cfg = { dids: [minted('did:plc:e')] }
  ok(repairStoreBackend(cfg).changed.length === 1, 'first pass changes it')
  ok(repairStoreBackend(cfg).changed.length === 0, 'second pass changes nothing')
}

console.log('\n' + pass + ' assertions passed')