OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-proto-peer commits / 5ceb3c5

0.3.121: mint on the overlay backend, not the legacy default
A minted identity could not post. hpp came up "signing enabled", the room was writable, the genesis commit was authored — and every write logged "appview: refusing to CREATE bytes for a roster DID". Observed on a handset 2026-08-25 with did:plc:zl5loetx46p6xusgc47h3mia. The entry omitted storeBackend. bootSwarmDevice defaults it to null and `if (storeBackend === 'overlay')` is the ONLY thing that builds the OverlayRepoStore and record overlay, so the DID booted on the legacy inline substrate — records in the autobase where {$oid,size} pointers belong, no record files — which is what the log meant by "firehose-only (noRepo) DID". Found by diffing the minted entry against the operator's live config rather than by reasoning: both hosted DIDs there are storeBackend:'overlay' and mine had the field absent. The diff was the bug. This is not a cosmetic default. device-node.js states that the first sign() after flipping such a DID to 'overlay' materializes an empty MST root and publishes a delete-every-record commit (the e2jev state, hyper-social#16 P1), and the anti-wipe guard cannot catch it because it looks for record FILES and a legacy DID has none. A freshly minted identity has no history to migrate, so starting on the current backend is free and skips the one migration that can destroy a repo. 9/9, 32/32. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5ceb3c536728bb3febf657b3aad39fb18382e871
by plankatron <fleeky@gmail.com> · 2026-08-25T23:29:26.000Z

Changed files (3) +37 -1

lib/mint.js +13 -0
……@@ -85,6 +85,19 @@ export async function mintIdentity ({ rootMnemonic = null, configFile = null, ha
8585 // The roster this device will be admitted against. It contains itself and nothing else: it is the
8686 // only device that exists. Co-owners are added later by the invite ceremony.
8787 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',
88101 // THE FIRST DEVICE HOLDS THE GENESIS. This is the opposite posture from coownerJoin's `joiner`, and
89102 // it is explicit for the reason recorded there: the creator election is "lowest-keyed member ==
90103 // self", so leaving the role to emerge from key ordering silently flips it when a device joins whose
package.json +1 -1
……@@ -1,6 +1,6 @@
11{
22 "name": "hyper-proto-peer",
3 "version": "0.3.120",
3 "version": "0.3.121",
44 "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.",
55 "type": "module",
66 "main": "lib/peer.js",
test/mint.test.mjs +23 -0
……@@ -50,6 +50,29 @@ test('mints a did:plc and writes a bootable dids[] entry', async (t) => {
5050 t.ok(e.deviceMnemonic, 'carries its own device mnemonic')
5151})
5252
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.
69test('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
5376test('⛔ the root mnemonic is returned, never written to disk', async (t) => {
5477 const cfgFile = tmpCfg('custody')
5578 const r = await mintIdentity({ rootMnemonic: ROOT, configFile: cfgFile })