| … | … | @@ -85,6 +85,19 @@ export async function mintIdentity ({ rootMnemonic = null, configFile = null, ha |
| 85 | 85 | // The roster this device will be admitted against. It contains itself and nothing else: it is the |
| 86 | 86 | // only device that exists. Co-owners are added later by the invite ceremony. |
| 87 | 87 | members: [devicePublicKey], |
| 88 | // ⛔ EXPLICIT, BECAUSE THE DEFAULT IS THE LEGACY BACKEND. bootSwarmDevice defaults storeBackend to | |
| 89 | // null and `if (storeBackend === 'overlay')` is the only thing that builds the OverlayRepoStore and | |
| 90 | // record overlay. Omitting it boots the DID on the legacy inline substrate — records in the autobase | |
| 91 | // where {$oid,size} pointers belong, and no record files — which reads as an EMPTY record set under | |
| 92 | // PointerSubstrate. device-node.js spells out the consequence: the first sign() after flipping such | |
| 93 | // a DID to 'overlay' materializes an empty MST root and publishes a delete-every-record commit, and | |
| 94 | // the anti-wipe guard cannot catch it because that guard looks for record FILES and there are none. | |
| 95 | // | |
| 96 | // Every live DID in the fleet is 'overlay'. A minted identity that omitted this logged | |
| 97 | // "firehose-only (noRepo) DID" and silently could not post — observed on a handset 2026-08-25. A | |
| 98 | // fresh identity has no history to migrate, so starting on the current backend is free and skips | |
| 99 | // the one migration that can destroy a repo. | |
| 100 | storeBackend: 'overlay', | |
| 88 | 101 | // THE FIRST DEVICE HOLDS THE GENESIS. This is the opposite posture from coownerJoin's `joiner`, and |
| 89 | 102 | // it is explicit for the reason recorded there: the creator election is "lowest-keyed member == |
| 90 | 103 | // self", so leaving the role to emerge from key ordering silently flips it when a device joins whose |
| … | … | @@ -1,6 +1,6 @@ |
| 1 | 1 | { |
| 2 | 2 | "name": "hyper-proto-peer", |
| 3 | "version": "0.3.120", | |
| 3 | "version": "0.3.121", | |
| 4 | 4 | "description": "Self-custody, P2P-native atproto peer — holds the user's key + content as Hypercores and authors+signs its own repo. The source of truth in the peer/bridge split (Model B); atproto-bridge projects it to bsky.", |
| 5 | 5 | "type": "module", |
| 6 | 6 | "main": "lib/peer.js", |
| … | … | @@ -50,6 +50,29 @@ test('mints a did:plc and writes a bootable dids[] entry', async (t) => { |
| 50 | 50 | t.ok(e.deviceMnemonic, 'carries its own device mnemonic') |
| 51 | 51 | }) |
| 52 | 52 | |
| 53 | // ⛔ THE BACKEND IS NOT OPTIONAL, AND ITS DEFAULT IS THE WRONG ONE. | |
| 54 | // | |
| 55 | // bootSwarmDevice defaults `storeBackend` to null, and `if (storeBackend === 'overlay')` is the ONLY | |
| 56 | // thing that builds the OverlayRepoStore + record overlay. An entry without it boots on the LEGACY | |
| 57 | // inline substrate: records live in the autobase where {$oid,size} pointers belong and there are no | |
| 58 | // record files at all. Under PointerSubstrate that reads as an EMPTY record set, and device-node.js is | |
| 59 | // explicit about what happens next — the first sign() after flipping such a DID to 'overlay' would | |
| 60 | // "materialize an empty MST root and publish a delete-every-record commit" (the e2jev state, | |
| 61 | // hyper-social#16 P1). The anti-wipe guard does not catch it, because that guard looks for record FILES | |
| 62 | // and a legacy DID has none. | |
| 63 | // | |
| 64 | // Measured, not assumed: both live DIDs in the operator's config are storeBackend:'overlay', and a | |
| 65 | // minted identity that omitted it logged "firehose-only (noRepo) DID" and could not post (2026-08-25). | |
| 66 | // | |
| 67 | // A freshly minted identity has no history to migrate, so starting on the current backend costs nothing | |
| 68 | // and skips the one migration that can destroy a repo. | |
| 69 | test('mints on the CURRENT record backend, not the legacy default', async (t) => { | |
| 70 | const cfgFile = tmpCfg('backend') | |
| 71 | await mintIdentity({ rootMnemonic: ROOT, configFile: cfgFile }) | |
| 72 | const e = JSON.parse(fs.readFileSync(cfgFile, 'utf8')).dids[0] | |
| 73 | t.is(e.storeBackend, 'overlay', 'storeBackend is overlay — the pointer substrate, matching every live DID') | |
| 74 | }) | |
| 75 | ||
| 53 | 76 | test('⛔ the root mnemonic is returned, never written to disk', async (t) => { |
| 54 | 77 | const cfgFile = tmpCfg('custody') |
| 55 | 78 | const r = await mintIdentity({ rootMnemonic: ROOT, configFile: cfgFile }) |