OverviewFilesCommitsIssuesDocsReleasesPeers

favela-android commits / 9bafda9

log this device's public key, so it can name itself
The device public key is the ONE value another machine needs to admit this phone: coowner-invite --device <hex> puts it in the DID's roster, and that roster is what StateBaseV3 checks before admitting a writer. It was written nowhere — not logged, not in any read RPC, and unreadable via adb on a release-signed app. So a handset holding a DID but missing from its roster could not name itself to the machine able to fix it, and every write failed with "this room lists members and this device is not among them". That cost an evening. Same mnemonic precedence coowner-join uses (per-DID key first, config root second): a key derived from the wrong source would admit nothing and send the next person round the same loop. Best-effort — a phone that cannot derive it still boots and reads. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
9bafda9ac5ce2cfa9e317dc64d622265dfd1cce9
by plankatron <fleeky@gmail.com> · 2026-08-12T00:17:04.000Z

Changed files (1) +19 -0

lib/node.js +19 -0
……@@ -86,6 +86,25 @@ async function boot () {
8686 // Not passing it is why a phone that had genuinely completed the ceremony served ZERO DIDs
8787 // (hyper-social #40 §3). hyper-proto-peer 0.3.110+ applies the same precedence coowner-join uses.
8888 state.cfgDids = cfg.dids || [] // handles live in the CONFIG, not on the booted node — keep them
89 // SAY WHAT THIS DEVICE IS. The device public key is the ONE value another machine needs in order to
90 // admit this phone (`coowner-invite --device <hex>` puts it in the DID's roster, which is what
91 // StateBaseV3 checks before it will admit a writer). It was written nowhere: not logged, not in any
92 // read RPC, and unreadable from adb on a release-signed app — so a phone that holds a DID but is
93 // absent from its roster could not even NAME itself to the machine able to fix that. Whole evening,
94 // 2026-08-11.
95 //
96 // Same mnemonic precedence coowner-join uses (per-DID key first, config root second): printing the
97 // key from the wrong source would send the next person round the same loop with a hex string that
98 // admits nothing. Best-effort — a phone that cannot derive this still boots and reads.
99 try {
100 const { deviceKeetKeyPair } = await import('atproto-identity/lib/device-chain.js')
101 for (const d of (cfg.dids || [])) {
102 const mn = d.deviceMnemonic || cfg.deviceMnemonic
103 if (!mn) continue
104 const kp = await deviceKeetKeyPair(mn)
105 log('device public key for ' + d.did, { devicePublicKey: b4a.toString(kp.publicKey, 'hex') })
106 }
107 } catch (e) { log('could not derive the device public key', { error: (e && e.message) || String(e) }) }
89108 state.hpp = await bootMultiDevice({ storage: dirs.hpp, dids: cfg.dids || [], deviceMnemonic: cfg.deviceMnemonic || null })
90109 // ⛔ REPORT WHAT BOOTED, NOT WHAT WAS CONFIGURED. This line used to print the CONFIG's DID count,
91110 // so it said "signing enabled {dids:1}" while zero nodes had come up — the compose button renders