OverviewFilesCommitsIssuesDocsReleasesPeers

ap-bridge commits / 9fbab28

0.9.91: a name the holder cannot see, re-claim, or release
`fleeky@ap.7rnx.net` was claimed from a handset on 2026-09-10 18:14:35. It is in registrations.json against that device's own feed key. Its webfinger and actor URL both answer 200. The phone's settings page said "1. Your name — none yet" on every boot for a day, and the fediverse switch read "no bridge carries you yet". ⛔ THE CLAIM SUCCEEDED AND THE DEVICE RECORDED NOTHING. favela-surface writes the name only when the bridge NAMES one — `if (r.acct) set-reach({ fediverse: r.acct })` — and admitClaim's 202 body was `{ username, generation, pending }`, with no `acct`. 202 is the path where the name IS claimed but holding the feed threw, so the one case that most needs recording was the one case that recorded nothing. A later bridge boot rehydrated the held feed from registrations.json and the actor went live, with the device still knowing nothing about it. Both success paths now answer through one `acctFor()`, because the bug was that they disagreed. ⛔ AND THERE WAS NO WAY BACK FROM THE DEVICE. Re-claiming returned `taken` — to the holder, whose chain proof admit.js had just verified — and the release page returns to /settings unless `reach.fediverse` is set, which is exactly what never got written. Held, invisible, unreachable, with every layer reporting success. So a matching root is now admitted rather than refused. Control is proved BEFORE claim(), so a matching root IS the holder, and answering "taken" to the person who holds it is not a guard, it is a trap. The generation deliberately does NOT move: it is what makes a REUSED name a different actor, and bumping it for the same holder would orphan every object id already federated under it — the impersonation-by-name-reuse guard turned on the person it exists to protect. The feed key IS refreshed, because one person can hold a name across a replaced device. ⚠ AN EXISTING TEST ASSERTED THE BUG. `⛔ a taken name is refused, not silently reassigned` claimed the same name twice, and `me()` is memoised — one identity — so it described a stranger and tested the holder. That is how refusing the holder survived as intended behaviour. It now uses a second identity, which is what its name always meant; a stranger is still refused 409 and the holder is unchanged. 261/261 pass. The three new assertions fail against the previous lib and the stranger case passes against both, which is the half that must not change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
9fbab28b8e0cc9d10baf06235015c21014513fb6
by plankatron <fleeky@gmail.com> · 2026-09-11T22:03:48.000Z

Changed files (6) +994 -137

.pgh-lock.json +810 -129
……@@ -1,9 +1,9 @@
11{
22 "name": "ap-bridge",
3 "version": "0.9.89",
3 "version": "0.9.90",
44 "runtime": "bare",
5 "rootTreeHash": "c27a6c758f36eae72d6c6b162df7867b77694bd953a4ade1240f8e9156214ed4",
6 "publishedAt": "2026-09-10T00:03:50.089Z",
5 "rootTreeHash": "6b2041d78a1d74336fff1aa05b710e533a0ae19a164f61783af79ca5bc421f31",
6 "publishedAt": "2026-09-11T16:48:10.194Z",
77 "deps": [
88 {
99 "name": "@hyperswarm/secret-stream",
……@@ -1846,9 +1846,9 @@
18461846 },
18471847 {
18481848 "name": "favela-identity",
1849 "version": "0.0.2",
1850 "tree": "7b3f314cf92558edbc90295c573885276a6add925318998c829c03d335edc765",
1851 "normHash": "764cabc80c7ebc250bf01507b94d7e0960fa26958305cae299a326913d716d5f",
1849 "version": "0.0.3",
1850 "tree": "f5a680144af3a0d302a8a7af1e4009d1d5bf9960a65d31f11595cd303a04498e",
1851 "normHash": "b2e516b78dd9288fb8986981eaae3b6904732c1ce927b2263f43af34d09691c6",
18521852 "workspace": true
18531853 },
18541854 {
……@@ -2395,9 +2395,9 @@
23952395 },
23962396 {
23972397 "name": "favela-peer",
2398 "version": "0.0.50",
2399 "tree": "d6422e37ce071e93589035c85bf6aea0db341b785fc763f6b6c77c3a3860586f",
2400 "normHash": "5c2a74403164d3d3a449fa1ac5898f98f408c40984a62780cbc84ac12864a54c",
2398 "version": "0.0.56",
2399 "tree": "facecf308fc9567acaf159e03fb313b3b3495ef8ffd8ea1981740d6d156bad78",
2400 "normHash": "774857dc8827b4eb191ee2b03dbf149afa2da9b891f6d50634e6f105b7fde75d",
24012401 "workspace": true
24022402 },
24032403 {
……@@ -2410,7 +2410,7 @@
24102410 {
24112411 "name": "anproto",
24122412 "version": "0.1.0",
2413 "tree": "ef3c43a7858ae5cbd82b7102dc5f330d38fc556d995d099c89c40c90fb0e3684",
2413 "tree": "35bb93f536c3a8089ee2dfc238c17dddfb9208cd11a059c0886262ab242a5a0b",
24142414 "normHash": "8d313660cbe4657820bede16f5e167b28e4f57e57a027fd4c00daf7b62fd63cd",
24152415 "workspace": true
24162416 },
……@@ -3454,6 +3454,185 @@
34543454 "normHash": "2c833f38bcd7ee1f2208d8965be025d5f9cd7a117b121ee74133552f25294484",
34553455 "workspace": false
34563456 },
3457 {
3458 "name": "hyperident",
3459 "version": "0.2.6",
3460 "tree": "a0bdbfd60e82d2f521ccf99c9df4a919a384bab87e5c74f008c17dc09bc7ba2f",
3461 "normHash": "7e74dbbe38fbc48c7c7f24c3f3f796ff0b529718d01feefb79201e9650895b55",
3462 "workspace": true
3463 },
3464 {
3465 "name": "bare-os",
3466 "version": "2.4.4",
3467 "tree": "cb2f018b4019fd85e32b7008f157f55e41a977b7210dd51bec7b96e20384b28e",
3468 "normHash": "993d50d943f557a641970768cc4fe277d3b420bbabd0a2f33f30838674ecbf82",
3469 "workspace": false,
3470 "prebuilds": {
3471 "android-arm": "76cae1adbd819fd9ead5fbc728941303f4251c3f3f01111abf54554bf2001f9e",
3472 "android-arm64": "4b6607c9e10b108232be86e87ffc2eaf3f14e86965c9ead1e5fc9666a52922bc",
3473 "android-ia32": "05c734cbfa3c9d1e7ad6d7daf8e3373eb3dda16ccd3543ef5e96a77d230ee3f4",
3474 "android-x64": "f0444a6a31b4710c4710e6feb4fe2eed4cdc10f652439f053adb1c835157fb9b",
3475 "darwin-arm64": "ff054689e98b5f1be2e0f788ca5a673c56471bfcdea2d0b067f5bfee6f2e6dc7",
3476 "darwin-x64": "b3124e601d32980eb0219de5cee4b29363ea595f2aa57d671dc8f73af816fc80",
3477 "ios-arm64": "e643c7f59b981d4fb1c0478087249c734f29039baf3cda31223f9e58b9d70013",
3478 "ios-arm64-simulator": "4a9da039ad26bba49b8f234bd4a0f2153ca376ea6e9a5f6595ee046218840475",
3479 "ios-x64-simulator": "ca6eb709d86b5f63e6ba8616a6dae99e3834a64481937162173fda08e235b14f",
3480 "linux-arm64": "5d80405b7444bf7776d3a54fa95e4b5621aaf4566172276b4d1abf25b0379e83",
3481 "linux-x64": "eafbf19219e1fb13b298735563690280b9d479526b237a8164e83cc669848e9b",
3482 "win32-arm64": "f2de213e7a97077da9b51c2694894805f0e45fdb99c414e0e84b495dac7d98db",
3483 "win32-x64": "9677d035bbd03882511d7c58585b301e719c18252c72404e071e01aa58597d81"
3484 }
3485 },
3486 {
3487 "name": "bare-path",
3488 "version": "2.1.3",
3489 "tree": "277fa04b3d4e0a88695daa41addb494ad849d5a7321aae48fa50c2ff3575273c",
3490 "normHash": "ca9ad156584ecbe83abacd1a85bd6b861a90a42acc7ce9ce7dc7e46e3559830d",
3491 "workspace": false
3492 },
3493 {
3494 "name": "bare-subprocess",
3495 "version": "3.1.4",
3496 "tree": "c5f18b3620514f54714aeb351f074adface216a335242e323e33d28d261376fe",
3497 "normHash": "43a6cb895749f501c58a66837659c58c13b79248d198b11f8b1b00b33917c54a",
3498 "workspace": false,
3499 "prebuilds": {
3500 "android-arm": "f85ffa30c774472dbe0b22bf71311984dc379f099ab6b72257ce053015516f8f",
3501 "android-arm64": "a2efa038594daf8d76bb3bb05710fd958b8bb735e01c394993014e59e0f41364",
3502 "android-ia32": "3bb5eb9e8454a60b808f99b020d809a27516a25befc7a17f53b2022c6cdd5034",
3503 "android-x64": "a07b6643e4668a4f3e84b0a1b4eef7ee44c39cff02ff7d2d1831b4f667981a77",
3504 "darwin-arm64": "0c33409f3da9fd965a8ee0e2b0c555978fad4cb2f2ba1ae1111576ec7ce0e65d",
3505 "darwin-x64": "04999d3ce6c3cd93629fbb62f850428cbb1ff01d6da5f718d2de1888fb8e668b",
3506 "ios-arm64": "2f1c463a9e1d593d37ef3eff5b20bc85e6af974cb6e8f6d179cd880c9075fc4d",
3507 "ios-arm64-simulator": "513362f4d98a7a04e44f2400c21135321762679a20f9f25f9f0fb6d0d9506bc5",
3508 "ios-x64-simulator": "a9d91e9fb44b21c3c65d300d66fb5023cc29fd84a57eccd01037c0b73396fa3c",
3509 "linux-arm64": "89d285437ae5bfce40fa9721f79772f67f2e6e9a8469a938e63ead8b9834a352",
3510 "linux-x64": "9062c9eb6a12ace70192924a2ae2ccd41455c5949c687ca59505c8307b7d1688",
3511 "win32-arm64": "9d3f1ece4b42895c11b78417582010dd1e1ca87ea78f7126a2acd59625222cc3",
3512 "win32-x64": "6cb729616d573cb25b047a11f7713c1bf90ce58199ad26518f19fa9c706fdbcc"
3513 }
3514 },
3515 {
3516 "name": "bare-env",
3517 "version": "2.1.0",
3518 "tree": "d63f99cd758e899132ab23ff18b4ef341152b3dadd51cbd8584c1213b431d487",
3519 "normHash": "c8e0def456fd18d6844fc790dccb538c973c40d7fb723e184341aa00841bbc5a",
3520 "workspace": false
3521 },
3522 {
3523 "name": "bare-pipe",
3524 "version": "3.3.8",
3525 "tree": "5953a1f81079276ab95313c9b4133345558adb232125c486777aa6158954f5b9",
3526 "normHash": "01d2f0c1ee319f117626e553ee4746f57e9126f59e2c2c98966c51266302b3cb",
3527 "workspace": false,
3528 "prebuilds": {
3529 "android-arm": "46c0942b854a04849603bb4376fd59424b6024d4bf241975da8de3a126d0e797",
3530 "android-arm64": "ea0c10b6523aacd462a0754ef988ce0ce43a089aff3d852b465c773e4b036efc",
3531 "android-ia32": "490948ca1d3a767bd841a3a710d18630f8d26d3d0d885470587088f9e70e4355",
3532 "android-x64": "a33347347574ea9395474cb3948818d1d5f97da317d66456086370639640c29f",
3533 "darwin-arm64": "a3a922f6ca2c5266ebe557d60dc42cc791f3bec60406dc7b2b43cc9d893ed336",
3534 "darwin-x64": "ef422e27fb752432df1d11c7338f150b1f3adfb4fc35a1fc1f2e4438cbbd9d4b",
3535 "ios-arm64": "4c901f0acf189c06ab7fdf69347de79fdd2e2d06cd43e0964714e43966d7886a",
3536 "ios-arm64-simulator": "0c2861c14d70e664ee8acb54a69d83af7f11e5ca4df29226140430aab1c5aaca",
3537 "ios-x64-simulator": "c2cf7fad90a9da61f527fcc448d9427b973b47d5decca2fc5c17c0062afb0512",
3538 "linux-arm64": "2a4bd1012ac7fde0f4c8a073b678dda750b4dcc6859fd06b11f29b619cad38b4",
3539 "linux-x64": "718e0eb16d3ac31b7c3112de804c1d935ffb28f87c406b80b730fede917c503e",
3540 "win32-arm64": "bff3ef77e16d010ce988b423b1665865a2e40b9649c6013a4efb32299ff1cd40",
3541 "win32-x64": "38070c20b01420ef25df118efd293fd56152a8514bb38a484392f34ec8496916"
3542 }
3543 },
3544 {
3545 "name": "blind-relay",
3546 "version": "1.4.0",
3547 "tree": "e92100a4f821a638ac99c1407e4c7e784f8eb9661e6d9119ecbec4faae77cc7e",
3548 "normHash": "030126024d051810159e501686120c5759ba2483289fbeaa8093bb259e008c76",
3549 "workspace": false
3550 },
3551 {
3552 "name": "bogon",
3553 "version": "1.2.0",
3554 "tree": "1f5de2daa08643a5cb8bce2153ca4d55d4504fb366f1f1ef80bca1700ec085f7",
3555 "normHash": "87c8dca1d81792afc4686524aa041812a737709e9fb7f32824ef37e03fe10572",
3556 "workspace": false
3557 },
3558 {
3559 "name": "compact-encoding-bitfield",
3560 "version": "1.0.0",
3561 "tree": "579bdc1eaabbca726d3916f1fc74cfd4986653f6748632db586817a2a8caabea",
3562 "normHash": "f434c400769425fb1ca995f4096c83036f881eb9506d70b776ecb94a3cf39f52",
3563 "workspace": false
3564 },
3565 {
3566 "name": "compact-encoding-net",
3567 "version": "1.2.0",
3568 "tree": "967cf214778dbacd7cc7cc3850ffdff6a6f145b4bd73de741d0938d7d84d33d5",
3569 "normHash": "c87929fef6210618cdeb9ea6e19e11c77a77c8ca1274aa32e231eb082b753092",
3570 "workspace": false
3571 },
3572 {
3573 "name": "dht-rpc",
3574 "version": "6.26.4",
3575 "tree": "67c4c05f69061f65a2548595c43282be2da8e0934e6ce347afa034b29cc40c81",
3576 "normHash": "54d4bc92a385c266f0225919234c5eac168c7614704b13f59dd1e75139a067a5",
3577 "workspace": false
3578 },
3579 {
3580 "name": "hyperdht",
3581 "version": "6.30.0",
3582 "tree": "fc36977bda7dd2c7467653aa2ef520890b3a0aae16870a9269f0f71691673dce",
3583 "normHash": "429b22b6f4cce71778bf115219863d8c775ef749f4d360148d72f03df62431ec",
3584 "workspace": false
3585 },
3586 {
3587 "name": "hyperdht-address",
3588 "version": "1.0.1",
3589 "tree": "bba869f5c85b11bce5671d57a87cd2793b490588867cd07fbe94cad796114970",
3590 "normHash": "df578f3052c3a2b94031bdfa7b3953492623e8635140ce9d807fbc9dd135f5c7",
3591 "workspace": false
3592 },
3593 {
3594 "name": "hyperschema",
3595 "version": "1.20.1",
3596 "tree": "d0099e1743480f28a08c7374d20ad5048088475180ab606626b63acd59e365a5",
3597 "normHash": "46977f73b99138138d4a68833c760cc70397e6e99a94f99221b9e0987e24035b",
3598 "workspace": false
3599 },
3600 {
3601 "name": "pear-interface",
3602 "version": "1.1.0",
3603 "tree": "34d0bdd07463a6e2405f13875a5e2aed437ed0becfb3924542ac2062b7a5cbaf",
3604 "normHash": "e1aa4a09de7991fc2b4e07bf67cbbdf88ed1f4d853fc1cb582820e39f7326628",
3605 "workspace": false
3606 },
3607 {
3608 "name": "protomux",
3609 "version": "3.10.1",
3610 "tree": "60429c7756f039ec2b3e5240f702dee450c1da19ad6de06900a3877c6c72bb52",
3611 "normHash": "7011cea58a13650cebd82908f26ec8c724892686fce37829f66aedfa9b4934aa",
3612 "workspace": false
3613 },
3614 {
3615 "name": "udx-native",
3616 "version": "1.19.2",
3617 "tree": "2ecc91a8c3595dda5dfc86ace62a70875520adc4e588a2ac14945dbc4313c450",
3618 "normHash": "e0f1d39ff27047c2a137b8e66d2c9a5a2fe03552557a8a42856d2846e0d1f3d5",
3619 "workspace": false,
3620 "prebuilds": {
3621 "android-arm": "9777bdb34468acf089b5715a57b366ad18b06bb63641848652205014cc154872",
3622 "android-arm64": "a390555806c20b12f2a5aac3b8bf3a8d1dcf8d8d695c24eec35e7c213a56fc08",
3623 "android-ia32": "d41e14dccc86c8f37727bd44dca98c4cc86bd788dcbd579d8cd76c7bda6b9f61",
3624 "android-x64": "b9e82a968a8b87b115420631b81e2a7cd170c1a94e8f860a67a85f070810e50e",
3625 "darwin-arm64": "cab4b8c29a1c0fad1abd99a23d993554e6acf03760d5f64f4023426de998cb27",
3626 "darwin-x64": "2e1d33eed937120353b51b7b00136fa1ca81377f87d20193db6574630c36dbb4",
3627 "ios-arm64": "50db6c9694a3885ed9f81928a426c43372989b6063ab3828ea5917b4d272e010",
3628 "ios-arm64-simulator": "8073d1bd65623e59bf5c1da5b3185c276b37701bac2226ace67e5f47c4bcc154",
3629 "ios-x64-simulator": "6e682d4fd2d969d45543cbb68c572d767347eae7d347b16a5c9227f8f10beac5",
3630 "linux-arm64": "04ca107b93195b7620c84bcf885451066736c569295e5e338ea34c2b7bfa9c48",
3631 "linux-x64": "7daf2e3a8ff336614c06d741a6c9b93966ef3ed11bca0b366ac5b8a209e510b4",
3632 "win32-arm64": "2bbf9b4222f03ee18b14bd6467b9f6c79500d700925a225c9be37be52dbea5d8",
3633 "win32-x64": "abe49e5b7258d53037d967fc691a4ef2f2e4fa99b58c74fcaa14446c0194f5a7"
3634 }
3635 },
34573636 {
34583637 "name": "tiny-buffer-map",
34593638 "version": "1.1.1",
……@@ -3559,28 +3738,6 @@
35593738 "win32-x64": "da2135bc307417a626e1908434723969bc6d0990b0d92457a3bc3e6b2d9c4761"
35603739 }
35613740 },
3562 {
3563 "name": "bare-pipe",
3564 "version": "3.3.8",
3565 "tree": "5953a1f81079276ab95313c9b4133345558adb232125c486777aa6158954f5b9",
3566 "normHash": "01d2f0c1ee319f117626e553ee4746f57e9126f59e2c2c98966c51266302b3cb",
3567 "workspace": false,
3568 "prebuilds": {
3569 "android-arm": "46c0942b854a04849603bb4376fd59424b6024d4bf241975da8de3a126d0e797",
3570 "android-arm64": "ea0c10b6523aacd462a0754ef988ce0ce43a089aff3d852b465c773e4b036efc",
3571 "android-ia32": "490948ca1d3a767bd841a3a710d18630f8d26d3d0d885470587088f9e70e4355",
3572 "android-x64": "a33347347574ea9395474cb3948818d1d5f97da317d66456086370639640c29f",
3573 "darwin-arm64": "a3a922f6ca2c5266ebe557d60dc42cc791f3bec60406dc7b2b43cc9d893ed336",
3574 "darwin-x64": "ef422e27fb752432df1d11c7338f150b1f3adfb4fc35a1fc1f2e4438cbbd9d4b",
3575 "ios-arm64": "4c901f0acf189c06ab7fdf69347de79fdd2e2d06cd43e0964714e43966d7886a",
3576 "ios-arm64-simulator": "0c2861c14d70e664ee8acb54a69d83af7f11e5ca4df29226140430aab1c5aaca",
3577 "ios-x64-simulator": "c2cf7fad90a9da61f527fcc448d9427b973b47d5decca2fc5c17c0062afb0512",
3578 "linux-arm64": "2a4bd1012ac7fde0f4c8a073b678dda750b4dcc6859fd06b11f29b619cad38b4",
3579 "linux-x64": "718e0eb16d3ac31b7c3112de804c1d935ffb28f87c406b80b730fede917c503e",
3580 "win32-arm64": "bff3ef77e16d010ce988b423b1665865a2e40b9649c6013a4efb32299ff1cd40",
3581 "win32-x64": "38070c20b01420ef25df118efd293fd56152a8514bb38a484392f34ec8496916"
3582 }
3583 },
35843741 {
35853742 "name": "bare-tcp",
35863743 "version": "1.9.1",
……@@ -3661,27 +3818,6 @@
36613818 "normHash": "b2b318faf03d9edbf7917d40ace2bae5b192f01ddc1af3f45e1b9b0b61198d25",
36623819 "workspace": false
36633820 },
3664 {
3665 "name": "bogon",
3666 "version": "1.2.0",
3667 "tree": "1f5de2daa08643a5cb8bce2153ca4d55d4504fb366f1f1ef80bca1700ec085f7",
3668 "normHash": "87c8dca1d81792afc4686524aa041812a737709e9fb7f32824ef37e03fe10572",
3669 "workspace": false
3670 },
3671 {
3672 "name": "compact-encoding-bitfield",
3673 "version": "1.0.0",
3674 "tree": "579bdc1eaabbca726d3916f1fc74cfd4986653f6748632db586817a2a8caabea",
3675 "normHash": "f434c400769425fb1ca995f4096c83036f881eb9506d70b776ecb94a3cf39f52",
3676 "workspace": false
3677 },
3678 {
3679 "name": "compact-encoding-net",
3680 "version": "1.2.0",
3681 "tree": "967cf214778dbacd7cc7cc3850ffdff6a6f145b4bd73de741d0938d7d84d33d5",
3682 "normHash": "c87929fef6210618cdeb9ea6e19e11c77a77c8ca1274aa32e231eb082b753092",
3683 "workspace": false
3684 },
36853821 {
36863822 "name": "corestore",
36873823 "version": "7.9.2",
……@@ -3689,13 +3825,6 @@
36893825 "normHash": "1dc9835f01cdf10d1d2496924fd522742635090f76ad77afd4ead5c86e1ae145",
36903826 "workspace": false
36913827 },
3692 {
3693 "name": "dht-rpc",
3694 "version": "6.26.4",
3695 "tree": "67c4c05f69061f65a2548595c43282be2da8e0934e6ce347afa034b29cc40c81",
3696 "normHash": "54d4bc92a385c266f0225919234c5eac168c7614704b13f59dd1e75139a067a5",
3697 "workspace": false
3698 },
36993828 {
37003829 "name": "hyper-rooms",
37013830 "version": "0.1.5",
……@@ -3804,20 +3933,6 @@
38043933 "normHash": "959e896f1f54e2837b7abbb208278c23940d208cf37edd5ceaecb17612d8e12f",
38053934 "workspace": false
38063935 },
3807 {
3808 "name": "hyperschema",
3809 "version": "1.20.1",
3810 "tree": "d0099e1743480f28a08c7374d20ad5048088475180ab606626b63acd59e365a5",
3811 "normHash": "46977f73b99138138d4a68833c760cc70397e6e99a94f99221b9e0987e24035b",
3812 "workspace": false
3813 },
3814 {
3815 "name": "protomux",
3816 "version": "3.10.1",
3817 "tree": "60429c7756f039ec2b3e5240f702dee450c1da19ad6de06900a3877c6c72bb52",
3818 "normHash": "7011cea58a13650cebd82908f26ec8c724892686fce37829f66aedfa9b4934aa",
3819 "workspace": false
3820 },
38213936 {
38223937 "name": "rocksdb-native",
38233938 "version": "3.15.0",
……@@ -3854,35 +3969,6 @@
38543969 "normHash": "8ef7d49b114cc1ef29ba913c97a2c393e2ccb714712d7d1eba79e468a6fab9cb",
38553970 "workspace": false
38563971 },
3857 {
3858 "name": "hyperdht-address",
3859 "version": "1.0.1",
3860 "tree": "bba869f5c85b11bce5671d57a87cd2793b490588867cd07fbe94cad796114970",
3861 "normHash": "df578f3052c3a2b94031bdfa7b3953492623e8635140ce9d807fbc9dd135f5c7",
3862 "workspace": false
3863 },
3864 {
3865 "name": "udx-native",
3866 "version": "1.19.2",
3867 "tree": "2ecc91a8c3595dda5dfc86ace62a70875520adc4e588a2ac14945dbc4313c450",
3868 "normHash": "e0f1d39ff27047c2a137b8e66d2c9a5a2fe03552557a8a42856d2846e0d1f3d5",
3869 "workspace": false,
3870 "prebuilds": {
3871 "android-arm": "9777bdb34468acf089b5715a57b366ad18b06bb63641848652205014cc154872",
3872 "android-arm64": "a390555806c20b12f2a5aac3b8bf3a8d1dcf8d8d695c24eec35e7c213a56fc08",
3873 "android-ia32": "d41e14dccc86c8f37727bd44dca98c4cc86bd788dcbd579d8cd76c7bda6b9f61",
3874 "android-x64": "b9e82a968a8b87b115420631b81e2a7cd170c1a94e8f860a67a85f070810e50e",
3875 "darwin-arm64": "cab4b8c29a1c0fad1abd99a23d993554e6acf03760d5f64f4023426de998cb27",
3876 "darwin-x64": "2e1d33eed937120353b51b7b00136fa1ca81377f87d20193db6574630c36dbb4",
3877 "ios-arm64": "50db6c9694a3885ed9f81928a426c43372989b6063ab3828ea5917b4d272e010",
3878 "ios-arm64-simulator": "8073d1bd65623e59bf5c1da5b3185c276b37701bac2226ace67e5f47c4bcc154",
3879 "ios-x64-simulator": "6e682d4fd2d969d45543cbb68c572d767347eae7d347b16a5c9227f8f10beac5",
3880 "linux-arm64": "04ca107b93195b7620c84bcf885451066736c569295e5e338ea34c2b7bfa9c48",
3881 "linux-x64": "7daf2e3a8ff336614c06d741a6c9b93966ef3ed11bca0b366ac5b8a209e510b4",
3882 "win32-arm64": "2bbf9b4222f03ee18b14bd6467b9f6c79500d700925a225c9be37be52dbea5d8",
3883 "win32-x64": "abe49e5b7258d53037d967fc691a4ef2f2e4fa99b58c74fcaa14446c0194f5a7"
3884 }
3885 },
38863972 {
38873973 "name": "hypercore",
38883974 "version": "11.35.1",
……@@ -3923,8 +4009,38 @@
39234009 "tree": "38f44af48b2d59b46f1b5db31b81d02f3ca8b8bad6bc0fb9fffff698241f5e37"
39244010 }
39254011 ],
3926 "reason": "dep version-splits only; 0 code-differs",
3927 "acceptedAt": "2026-09-10T00:03:50.127Z"
4012 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4013 "acceptedAt": "2026-09-11T16:48:10.217Z"
4014 },
4015 {
4016 "type": "VERSION_CONFLICT",
4017 "dep": "@noble/hashes",
4018 "newVersion": "1.8.0",
4019 "newTree": "5847af868bc88051272785234d77af86085374ec9079fed3e7732724cd9c8254",
4020 "existing": [
4021 {
4022 "appName": "hypernote",
4023 "version": "1.3.3",
4024 "tree": "ee91b6bd77101e3404ca571b2e5adf0f47029d174398a05f50294fc207e39d99"
4025 }
4026 ],
4027 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4028 "acceptedAt": "2026-09-11T16:48:10.217Z"
4029 },
4030 {
4031 "type": "VERSION_CONFLICT",
4032 "dep": "bare-buffer",
4033 "newVersion": "3.7.1",
4034 "newTree": "5b662871145437a17e36c1dbebd64fc6e29be649b168ffb7638d3c5eaa781443",
4035 "existing": [
4036 {
4037 "appName": "hypernote",
4038 "version": "3.4.4",
4039 "tree": "3bb78d8f06263a549bd1077f37a7df257b1d557e44608631435af625f05a9c11"
4040 }
4041 ],
4042 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4043 "acceptedAt": "2026-09-11T16:48:10.217Z"
39284044 },
39294045 {
39304046 "type": "VERSION_CONFLICT",
……@@ -3936,10 +4052,195 @@
39364052 "appName": "pear-git-v3",
39374053 "version": "5.2.3",
39384054 "tree": "58c0bf8698af19464d0ee8354a77ed1ac96d3a5fa18aa4e794686cfbbf080e5d"
4055 },
4056 {
4057 "appName": "hypernote",
4058 "version": "5.2.3",
4059 "tree": "58c0bf8698af19464d0ee8354a77ed1ac96d3a5fa18aa4e794686cfbbf080e5d"
4060 }
4061 ],
4062 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4063 "acceptedAt": "2026-09-11T16:48:10.217Z"
4064 },
4065 {
4066 "type": "VERSION_CONFLICT",
4067 "dep": "bare-crypto",
4068 "newVersion": "1.15.3",
4069 "newTree": "c82ab3af512e791d52d721ee8c40199926fc649f34164d340d019b89b5c5a2bc",
4070 "existing": [
4071 {
4072 "appName": "hypernote",
4073 "version": "1.13.2",
4074 "tree": "da4c6b2654e9d66f10b0faa989311cf182878c24ab51b1c41458cfbf37ad29cd"
4075 }
4076 ],
4077 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4078 "acceptedAt": "2026-09-11T16:48:10.217Z"
4079 },
4080 {
4081 "type": "VERSION_CONFLICT",
4082 "dep": "bare-format",
4083 "newVersion": "1.0.2",
4084 "newTree": "a5309948aded2b30875eee7d9eb2c997f3fa01d43eb0c1fd90484cf68cd8b5d5",
4085 "existing": [
4086 {
4087 "appName": "hypernote",
4088 "version": "1.0.1",
4089 "tree": "40be8f559a13b54ebf0689e63e6c70f0d218035359e17c497ed7bc8a85c3cc95"
4090 }
4091 ],
4092 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4093 "acceptedAt": "2026-09-11T16:48:10.217Z"
4094 },
4095 {
4096 "type": "VERSION_CONFLICT",
4097 "dep": "bare-fs",
4098 "newVersion": "4.8.1",
4099 "newTree": "3358c77ab49da8137602bca8d21e56bb0d90916a237250446d6ee7724d8b949e",
4100 "existing": [
4101 {
4102 "appName": "hypernote",
4103 "version": "4.5.4",
4104 "tree": "3eae85efce82299769898c8151b5ed0a94e5a5ebdbcdee2105b815ca733a61cb"
4105 }
4106 ],
4107 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4108 "acceptedAt": "2026-09-11T16:48:10.217Z"
4109 },
4110 {
4111 "type": "VERSION_CONFLICT",
4112 "dep": "bare-http-parser",
4113 "newVersion": "2.1.4",
4114 "newTree": "e6991084a57472974a7f19d371cdfa226c28bd92be2d3b5a7ba38eabba8b3bb5",
4115 "existing": [
4116 {
4117 "appName": "hypernote",
4118 "version": "1.0.1",
4119 "tree": "8d9e01d443a127781773598d2e77c3f372334fad09e20623a819dabe49b22e1a"
4120 }
4121 ],
4122 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4123 "acceptedAt": "2026-09-11T16:48:10.217Z"
4124 },
4125 {
4126 "type": "VERSION_CONFLICT",
4127 "dep": "bare-http1",
4128 "newVersion": "4.6.1",
4129 "newTree": "ad81cb00dc100cbacbe692462fefc6cd1d676e4b6022e3b9fa2d9294a4461aac",
4130 "existing": [
4131 {
4132 "appName": "hypernote",
4133 "version": "4.2.3",
4134 "tree": "dc1ad6c999fe90d946ba0ef34698ea9af451b639be2ad1c5b61eabb08337a6ca"
4135 }
4136 ],
4137 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4138 "acceptedAt": "2026-09-11T16:48:10.217Z"
4139 },
4140 {
4141 "type": "VERSION_CONFLICT",
4142 "dep": "bare-https",
4143 "newVersion": "3.1.0",
4144 "newTree": "5e9cf8230c28d6b667d121cb23a5202b5d5d44e12dbeef0a7d6fbcc55621bb3d",
4145 "existing": [
4146 {
4147 "appName": "hypernote",
4148 "version": "2.1.2",
4149 "tree": "88b4779570e5113aa21b204989ac018d1628b387681f7e53428a7c99114fb701"
4150 }
4151 ],
4152 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4153 "acceptedAt": "2026-09-11T16:48:10.217Z"
4154 },
4155 {
4156 "type": "VERSION_CONFLICT",
4157 "dep": "bare-net",
4158 "newVersion": "2.3.3",
4159 "newTree": "4e55af5c11c37b80f1d5a1f3bf2a0f9b8f8857d7688719dffe00cff16736ccf6",
4160 "existing": [
4161 {
4162 "appName": "hypernote",
4163 "version": "2.2.0",
4164 "tree": "720b867ec4300a3caa2325eb1449499f9940bfb88341ecceb5992fa0fdf5f37d"
4165 }
4166 ],
4167 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4168 "acceptedAt": "2026-09-11T16:48:10.217Z"
4169 },
4170 {
4171 "type": "VERSION_CONFLICT",
4172 "dep": "bare-os",
4173 "newVersion": "3.9.3",
4174 "newTree": "e811e3d7014f67fba132cd31556892864227232560ae0fda3f3123f4a75c2992",
4175 "existing": [
4176 {
4177 "appName": "hypernote",
4178 "version": "3.6.2",
4179 "tree": "b697fe174482a708b49d0764cd4786291845f0034a31e0699381ca7e7b269d04"
4180 }
4181 ],
4182 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4183 "acceptedAt": "2026-09-11T16:48:10.217Z"
4184 },
4185 {
4186 "type": "VERSION_CONFLICT",
4187 "dep": "bare-pipe",
4188 "newVersion": "4.3.1",
4189 "newTree": "08013f3f76e93f236b04c5a804dfd12debdc44bc2b5a5a5010f190f96ad682ec",
4190 "existing": [
4191 {
4192 "appName": "hypernote",
4193 "version": "4.1.3",
4194 "tree": "767f07242277ae80bd395de5a4969fcdbf956c216158ae517fb2b562558ac1a4"
4195 }
4196 ],
4197 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4198 "acceptedAt": "2026-09-11T16:48:10.217Z"
4199 },
4200 {
4201 "type": "VERSION_CONFLICT",
4202 "dep": "bare-process",
4203 "newVersion": "4.5.1",
4204 "newTree": "6d77305da357bc79789ae5f37818f9ca5f41c71ec27edacc74ce527c88b7e8c1",
4205 "existing": [
4206 {
4207 "appName": "hypernote",
4208 "version": "4.2.2",
4209 "tree": "de7deed21f587ffc8bde4971abe00fca77bb6c71c4e7effb31ad34ee330815df"
4210 }
4211 ],
4212 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4213 "acceptedAt": "2026-09-11T16:48:10.217Z"
4214 },
4215 {
4216 "type": "VERSION_CONFLICT",
4217 "dep": "bare-semver",
4218 "newVersion": "1.1.0",
4219 "newTree": "a823aa1d9b306b2af0c80660fb5a63a5a5adbc5dfa003f887fd482112c906025",
4220 "existing": [
4221 {
4222 "appName": "hypernote",
4223 "version": "1.0.2",
4224 "tree": "2df42517ebf73cd9c11f2f652f75e87401fd4ed7b856c0adc15c33baf1d4f1ad"
4225 }
4226 ],
4227 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4228 "acceptedAt": "2026-09-11T16:48:10.217Z"
4229 },
4230 {
4231 "type": "VERSION_CONFLICT",
4232 "dep": "bare-stream",
4233 "newVersion": "2.13.4",
4234 "newTree": "c61803ebf372fc64d3549eb1043087f3a531e95c2217768226320e09eb373077",
4235 "existing": [
4236 {
4237 "appName": "hypernote",
4238 "version": "2.8.0",
4239 "tree": "325aca171cbff560487177584078c8066133e91176639ca47c98d70e195aa92e"
39394240 }
39404241 ],
3941 "reason": "dep version-splits only; 0 code-differs",
3942 "acceptedAt": "2026-09-10T00:03:50.127Z"
4242 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4243 "acceptedAt": "2026-09-11T16:48:10.217Z"
39434244 },
39444245 {
39454246 "type": "VERSION_CONFLICT",
……@@ -3961,10 +4262,15 @@
39614262 "appName": "pgh-sidecar",
39624263 "version": "1.5.5",
39634264 "tree": "bdebba5a532ce4846b49f1e5ffad774becfc44182d0c3737f7e441ab42f4c43d"
4265 },
4266 {
4267 "appName": "hypernote",
4268 "version": "1.5.2",
4269 "tree": "89a40400106bf4fef95fb5c44a66ee29aada6f76c7740bfba09d8aeeb841f39c"
39644270 }
39654271 ],
3966 "reason": "dep version-splits only; 0 code-differs",
3967 "acceptedAt": "2026-09-10T00:03:50.127Z"
4272 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4273 "acceptedAt": "2026-09-11T16:48:10.217Z"
39684274 },
39694275 {
39704276 "type": "VERSION_CONFLICT",
……@@ -3973,28 +4279,338 @@
39734279 "newTree": "d31788ffa334976153023c6de84e5020b2009f51f9d472d0c2f09a13a53fc2b6",
39744280 "existing": [
39754281 {
3976 "appName": "hyperident",
3977 "version": "3.1.4",
3978 "tree": "c5f18b3620514f54714aeb351f074adface216a335242e323e33d28d261376fe"
4282 "appName": "hypernote",
4283 "version": "5.2.2",
4284 "tree": "1bd0eb382789c62cd4622d47bffb60984fdd766260c79f73a2fc7a136f3edbe9"
4285 }
4286 ],
4287 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4288 "acceptedAt": "2026-09-11T16:48:10.217Z"
4289 },
4290 {
4291 "type": "VERSION_CONFLICT",
4292 "dep": "bare-tls",
4293 "newVersion": "3.1.10",
4294 "newTree": "e693499377f146eec5788fa2887c4e42614f7f2efd3d5848d9019deec86c6632",
4295 "existing": [
4296 {
4297 "appName": "hypernote",
4298 "version": "2.1.7",
4299 "tree": "4526fe9dc5468264c7171ec16e9303726674b7b44ca0b3e09cb8b9a6a1be6169"
4300 }
4301 ],
4302 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4303 "acceptedAt": "2026-09-11T16:48:10.217Z"
4304 },
4305 {
4306 "type": "VERSION_CONFLICT",
4307 "dep": "bare-tty",
4308 "newVersion": "5.2.1",
4309 "newTree": "d99fbe108f5700f5e73d908e2d3d39ed44021b11bc74e1f152ebb493719b9eaa",
4310 "existing": [
4311 {
4312 "appName": "hypernote",
4313 "version": "5.0.3",
4314 "tree": "32b291cd756e142da18a6ed01c351e024fcedc3e2b1ecfbfb85f3106ada4419b"
4315 }
4316 ],
4317 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4318 "acceptedAt": "2026-09-11T16:48:10.217Z"
4319 },
4320 {
4321 "type": "VERSION_CONFLICT",
4322 "dep": "bare-url",
4323 "newVersion": "2.5.4",
4324 "newTree": "99519af1452440489f560cb045aba0ad314bbae19632f1184cdf91d5946571f5",
4325 "existing": [
4326 {
4327 "appName": "hypernote",
4328 "version": "2.3.2",
4329 "tree": "4a8cd3b0093092b33088617dc0415e8179882d7f2f13b7649962ca20b08beeed"
4330 }
4331 ],
4332 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4333 "acceptedAt": "2026-09-11T16:48:10.217Z"
4334 },
4335 {
4336 "type": "VERSION_CONFLICT",
4337 "dep": "bare-utils",
4338 "newVersion": "1.6.0",
4339 "newTree": "c05399d286dc85281bcaa88572c3d9c54ad631a50ecacfc50c0b73451b2a18a8",
4340 "existing": [
4341 {
4342 "appName": "hypernote",
4343 "version": "1.5.1",
4344 "tree": "4f63f165ee6a581f689b40c4a40df4df01ab6fba17d46bd29e40ef5bbdd9884f"
4345 }
4346 ],
4347 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4348 "acceptedAt": "2026-09-11T16:48:10.217Z"
4349 },
4350 {
4351 "type": "VERSION_CONFLICT",
4352 "dep": "bare-ws",
4353 "newVersion": "3.1.0",
4354 "newTree": "9c22db1f4e3137df4e44ba33887b65ce280bfc93454f8b91d2e41b458452e86b",
4355 "existing": [
4356 {
4357 "appName": "hypernote",
4358 "version": "2.0.4",
4359 "tree": "735109c736c0bd3413ab47414fd8b6ea9a99c401fe8e6d608459c79293806754"
4360 }
4361 ],
4362 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4363 "acceptedAt": "2026-09-11T16:48:10.217Z"
4364 },
4365 {
4366 "type": "VERSION_CONFLICT",
4367 "dep": "compact-encoding",
4368 "newVersion": "3.4.0",
4369 "newTree": "7579a2d3581169529b52ec241bf982d2d0c1b2fe24f8b64873baed5e3595436f",
4370 "existing": [
4371 {
4372 "appName": "hypernote",
4373 "version": "2.19.0",
4374 "tree": "b32b75bf1b2acdd18ed0cc370d01d477fba42ba09fd1f2f78af3ebad925e72d5"
4375 }
4376 ],
4377 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4378 "acceptedAt": "2026-09-11T16:48:10.217Z"
4379 },
4380 {
4381 "type": "VERSION_CONFLICT",
4382 "dep": "keet-identity-key",
4383 "newVersion": "3.2.0",
4384 "newTree": "f49a14a1e514c3b607ba0211e7d993e37a387ad60456831abaff3c5eae917114",
4385 "existing": [
4386 {
4387 "appName": "wirecore",
4388 "version": "3.1.0",
4389 "tree": "2adc29428c08a3a6cf227eba2d06ccdccaf5df83f47404f7f9c1da5f4b05dc80"
4390 },
4391 {
4392 "appName": "hypernote",
4393 "version": "3.1.0",
4394 "tree": "189397178121e9a18430943345dab820691648a324229071ceb980bbb77a7393"
4395 }
4396 ],
4397 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4398 "acceptedAt": "2026-09-11T16:48:10.217Z"
4399 },
4400 {
4401 "type": "VERSION_CONFLICT",
4402 "dep": "paparam",
4403 "newVersion": "1.13.0",
4404 "newTree": "4d8d5a7eb968560e887d7c9d097a5326b281b67b04e69a5e6b34514985e861ae",
4405 "existing": [
4406 {
4407 "appName": "hypernote",
4408 "version": "1.10.0",
4409 "tree": "3bedca5287a9041fc2fd5ec5ace4df1fa86240e6afec0a105ba67398bafc09a5"
4410 }
4411 ],
4412 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4413 "acceptedAt": "2026-09-11T16:48:10.217Z"
4414 },
4415 {
4416 "type": "VERSION_CONFLICT",
4417 "dep": "picomatch",
4418 "newVersion": "4.0.7",
4419 "newTree": "7236258ca920c2f9853c24ead4286ec92a5f709fdf0117f38257e9702752d3ce",
4420 "existing": [
4421 {
4422 "appName": "hypernote",
4423 "version": "4.0.3",
4424 "tree": "b9ca9b272008bd67672a1b252390e8b527095c46b46cf6b6725a7a187eb038b6"
4425 }
4426 ],
4427 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4428 "acceptedAt": "2026-09-11T16:48:10.217Z"
4429 },
4430 {
4431 "type": "VERSION_CONFLICT",
4432 "dep": "require-addon",
4433 "newVersion": "1.2.0",
4434 "newTree": "c51c5fb701d40f980004f1e17c5ae02d0a7657b4731292ec63d5ae7814acf355",
4435 "existing": [
4436 {
4437 "appName": "wirecore",
4438 "version": "1.3.0",
4439 "tree": "7793645d7d4248d6d6533e3cbcd4bf40bed4f38c678b0ca0606c15640ba5e7b9"
4440 }
4441 ],
4442 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4443 "acceptedAt": "2026-09-11T16:48:10.217Z"
4444 },
4445 {
4446 "type": "VERSION_CONFLICT",
4447 "dep": "sodium-native",
4448 "newVersion": "5.1.0",
4449 "newTree": "5552185a3644b46195a7a31c2cc21fca5eced9a552c541736120ed65900feff5",
4450 "existing": [
4451 {
4452 "appName": "hypernote",
4453 "version": "5.0.10",
4454 "tree": "79447513d4ac0357c8d60bdc7b81b3c5143d761b26700340cba266c4d1cb77b0"
4455 }
4456 ],
4457 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4458 "acceptedAt": "2026-09-11T16:48:10.217Z"
4459 },
4460 {
4461 "type": "VERSION_CONFLICT",
4462 "dep": "streamx",
4463 "newVersion": "2.28.1",
4464 "newTree": "2cb964b1c3ebf23b9605c6971c3ff1782b97d48730574458198f630d770f4cbe",
4465 "existing": [
4466 {
4467 "appName": "hypernote",
4468 "version": "2.23.0",
4469 "tree": "279d4e0447eb79fd75816864ba7f41f2df03080c9da69ea4603fe2900c8d9e53"
4470 }
4471 ],
4472 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4473 "acceptedAt": "2026-09-11T16:48:10.217Z"
4474 },
4475 {
4476 "type": "VERSION_CONFLICT",
4477 "dep": "corestore",
4478 "newVersion": "7.12.3",
4479 "newTree": "955c2e26f31af120554761af5ea65e6773648f137083594bb888429432200344",
4480 "existing": [
4481 {
4482 "appName": "wirecore",
4483 "version": "6.18.4",
4484 "tree": "ed4e0b480417610e195ff0b59e5b9a453abbf2db6d6ce72d7cd522269f835e9a"
4485 }
4486 ],
4487 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4488 "acceptedAt": "2026-09-11T16:48:10.217Z"
4489 },
4490 {
4491 "type": "VERSION_CONFLICT",
4492 "dep": "hypercore",
4493 "newVersion": "11.35.4",
4494 "newTree": "a8699972843ff0e9e0b134447b71b055bbd8acd4a6ea3187624a7a52931ce10e",
4495 "existing": [
4496 {
4497 "appName": "wirecore",
4498 "version": "10.38.2",
4499 "tree": "50137bc8c268148b89c11b15cb8918375338c3ea14c6507c3bce89e3cc91ceba"
4500 },
4501 {
4502 "appName": "hypernote",
4503 "version": "11.27.17",
4504 "tree": "a9e07d54e526c838dfe5e27df33936682aefb218f61a45b6c15289243999f648"
4505 }
4506 ],
4507 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4508 "acceptedAt": "2026-09-11T16:48:10.217Z"
4509 },
4510 {
4511 "type": "VERSION_CONFLICT",
4512 "dep": "hypercore-storage",
4513 "newVersion": "3.2.1",
4514 "newTree": "46b1c3d46b4bf7703f0baf537ffbb80489394e28737a5c2e1641ca94ac115c4c",
4515 "existing": [
4516 {
4517 "appName": "hypernote",
4518 "version": "2.7.1",
4519 "tree": "9d84c86441fe138b227c4fd01d1e948aa83bf8d13cfb1aa957838feea05fbb57"
4520 }
4521 ],
4522 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4523 "acceptedAt": "2026-09-11T16:48:10.217Z"
4524 },
4525 {
4526 "type": "VERSION_CONFLICT",
4527 "dep": "hyperschema",
4528 "newVersion": "1.23.0",
4529 "newTree": "c352064599040eaa58e03c28aa235e3ce7b1155bfb5b1c422b7ff98180099857",
4530 "existing": [
4531 {
4532 "appName": "wirecore",
4533 "version": "1.24.0",
4534 "tree": "4a3afbe2fc7a882e24454eb2e7e7cfe6816811ee5d73ae0f92e8b9ea9cfb8c78"
4535 }
4536 ],
4537 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4538 "acceptedAt": "2026-09-11T16:48:10.217Z"
4539 },
4540 {
4541 "type": "VERSION_CONFLICT",
4542 "dep": "index-encoder",
4543 "newVersion": "3.5.0",
4544 "newTree": "9d7780c45bc16e68fb1931323ee89c6ac8413a311f140c3ffbf846035e91693d",
4545 "existing": [
4546 {
4547 "appName": "hypernote",
4548 "version": "3.4.0",
4549 "tree": "1e5c5810d8971a3c27fde7fb0e4d633dcabbe2cf98d8df86b473f8d67eead8b9"
4550 }
4551 ],
4552 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4553 "acceptedAt": "2026-09-11T16:48:10.217Z"
4554 },
4555 {
4556 "type": "VERSION_CONFLICT",
4557 "dep": "protomux",
4558 "newVersion": "3.11.0",
4559 "newTree": "9afec1f15a5bf7dd535eeb082e0ec4c60f5fd425a44977bfb1e0f0aa635d342b",
4560 "existing": [
4561 {
4562 "appName": "wirecore",
4563 "version": "3.12.0",
4564 "tree": "e3478f50e85ba0ac288efed83e84c992483904897dc39ccf243d20a4ea25d972"
4565 }
4566 ],
4567 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4568 "acceptedAt": "2026-09-11T16:48:10.217Z"
4569 },
4570 {
4571 "type": "VERSION_CONFLICT",
4572 "dep": "rocksdb-native",
4573 "newVersion": "3.17.4",
4574 "newTree": "db9a29b3a3c27cca5ad561f21995e04945465c98d438579beff71e029e662776",
4575 "existing": [
4576 {
4577 "appName": "hypernote",
4578 "version": "3.13.0",
4579 "tree": "c05d8bf7eab5024ea98140f78c84d12fb494b4b7b10c7eedeb50b2be842fa519"
39794580 }
39804581 ],
3981 "reason": "dep version-splits only; 0 code-differs",
3982 "acceptedAt": "2026-09-10T00:03:50.127Z"
4582 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4583 "acceptedAt": "2026-09-11T16:48:10.217Z"
39834584 },
39844585 {
39854586 "type": "VERSION_CONFLICT",
3986 "dep": "blind-relay",
3987 "newVersion": "1.6.1",
3988 "newTree": "bfbfcab6c72c2aba5d6d9d8394454d69ef55adb58001b0b232d8d8a3bde6aeea",
4587 "dep": "safety-catch",
4588 "newVersion": "1.0.3",
4589 "newTree": "ff3d215ed5100cbae606310a5e795aaed09dc5505bee24e929d64898b78761d8",
39894590 "existing": [
39904591 {
3991 "appName": "hyperident",
3992 "version": "1.4.0",
3993 "tree": "e92100a4f821a638ac99c1407e4c7e784f8eb9661e6d9119ecbec4faae77cc7e"
4592 "appName": "hypernote",
4593 "version": "1.0.2",
4594 "tree": "0697bb338bbcec517353cffcdb2345a8232b9b90cc627a549a306f0323576390"
39944595 }
39954596 ],
3996 "reason": "dep version-splits only; 0 code-differs",
3997 "acceptedAt": "2026-09-10T00:03:50.127Z"
4597 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4598 "acceptedAt": "2026-09-11T16:48:10.218Z"
4599 },
4600 {
4601 "type": "VERSION_CONFLICT",
4602 "dep": "dht-rpc",
4603 "newVersion": "6.27.0",
4604 "newTree": "b8a0963abe945df0c795a3e086562a4c3e53d569cff054894942e3e772733ec2",
4605 "existing": [
4606 {
4607 "appName": "hypernote",
4608 "version": "6.26.3",
4609 "tree": "a366a73e74f8d80f106465058083b62bb70b833c721047124e99c89880e5c706"
4610 }
4611 ],
4612 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4613 "acceptedAt": "2026-09-11T16:48:10.218Z"
39984614 },
39994615 {
40004616 "type": "VERSION_CONFLICT",
……@@ -4003,13 +4619,63 @@
40034619 "newTree": "f4e87130e4397b037b121356f2db0161fadd8f2c320f7042fa31f4b5308193ec",
40044620 "existing": [
40054621 {
4006 "appName": "hyperident",
4007 "version": "6.30.0",
4008 "tree": "fc36977bda7dd2c7467653aa2ef520890b3a0aae16870a9269f0f71691673dce"
4622 "appName": "hypernote",
4623 "version": "6.29.0",
4624 "tree": "7912e790c9e87e9829a6daa037c5f5da0cc4355214f51d334f96e57678dfddeb"
4625 }
4626 ],
4627 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4628 "acceptedAt": "2026-09-11T16:48:10.218Z"
4629 },
4630 {
4631 "type": "VERSION_CONFLICT",
4632 "dep": "hyperswarm",
4633 "newVersion": "4.17.0",
4634 "newTree": "b668835d94500d16f2c5fecb5f5842620360c9991e26ec081c49fbf7e42c36c5",
4635 "existing": [
4636 {
4637 "appName": "wirecore",
4638 "version": "4.17.1",
4639 "tree": "003ac841449fcdda1abc3c87fee291a5d9596d245925a7298bd8ef5f8e423eae"
40094640 }
40104641 ],
4011 "reason": "dep version-splits only; 0 code-differs",
4012 "acceptedAt": "2026-09-10T00:03:50.127Z"
4642 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4643 "acceptedAt": "2026-09-11T16:48:10.218Z"
4644 },
4645 {
4646 "type": "VERSION_CONFLICT",
4647 "dep": "favela-identity",
4648 "newVersion": "0.0.3",
4649 "newTree": "f5a680144af3a0d302a8a7af1e4009d1d5bf9960a65d31f11595cd303a04498e",
4650 "existing": [
4651 {
4652 "appName": "favela-peer",
4653 "version": "0.0.2",
4654 "tree": "7b3f314cf92558edbc90295c573885276a6add925318998c829c03d335edc765"
4655 },
4656 {
4657 "appName": "favela-bridge",
4658 "version": "0.0.2",
4659 "tree": "7b3f314cf92558edbc90295c573885276a6add925318998c829c03d335edc765"
4660 }
4661 ],
4662 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4663 "acceptedAt": "2026-09-11T16:48:10.218Z"
4664 },
4665 {
4666 "type": "VERSION_CONFLICT",
4667 "dep": "blind-pairing-core",
4668 "newVersion": "2.10.1",
4669 "newTree": "645ea490be09f10f5bba3dd728574604c63fa40ca61723ec74b9e449605d48d1",
4670 "existing": [
4671 {
4672 "appName": "hypernote",
4673 "version": "2.10.0",
4674 "tree": "c486fe73805bf013e9c5de91b5780d1aa5d2b2295306f411f7863757aaa05e60"
4675 }
4676 ],
4677 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4678 "acceptedAt": "2026-09-11T16:48:10.218Z"
40134679 },
40144680 {
40154681 "type": "VERSION_CONFLICT",
……@@ -4023,8 +4689,23 @@
40234689 "tree": "10193a86e2c6f80c2d4377379a0aae4a959f8a40fd9e852b85fee081bfe6675a"
40244690 }
40254691 ],
4026 "reason": "dep version-splits only; 0 code-differs",
4027 "acceptedAt": "2026-09-10T00:03:50.127Z"
4692 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4693 "acceptedAt": "2026-09-11T16:48:10.218Z"
4694 },
4695 {
4696 "type": "VERSION_CONFLICT",
4697 "dep": "bare-zlib",
4698 "newVersion": "1.4.0",
4699 "newTree": "79d82655bc02fcfbc7d118ed2d64ad8f5914720192017140dccacea98601652d",
4700 "existing": [
4701 {
4702 "appName": "hypernote",
4703 "version": "1.3.1",
4704 "tree": "20b1ecd2937f3e35102892288234b86a8ec6deb3711016d96ff3509474ac465b"
4705 }
4706 ],
4707 "reason": "version-splits only, 0 code-differs; favela-identity 0.0.3 is new in this release and no already-published app uses the bridge verbs",
4708 "acceptedAt": "2026-09-11T16:48:10.218Z"
40284709 }
40294710 ]
40304711}
lib/admit.js +15 -2
……@@ -14,6 +14,14 @@
1414// operator, 403 → you did not prove control) and inventing a second vocabulary for the swarm would
1515// mean two things to keep in step.
1616
17// The name a person actually types, or null when this bridge has no host configured. One helper
18// because BOTH success paths must answer it the same way — they did not, and the difference was the
19// bug.
20function acctFor (config, username) {
21 const host = config && config.host
22 return host ? username + '@' + host : null
23}
24
1725export async function admitClaim ({ registrations, admission, config, onRegistered }, params = {}) {
1826 if (!registrations) return { status: 404, body: { error: 'not found' } }
1927 if (!admission) return { status: 503, body: { error: 'registration is not configured on this bridge' } }
……@@ -52,7 +60,12 @@ export async function admitClaim ({ registrations, admission, config, onRegister
5260 // ⚠ The name IS claimed at this point. Reporting success would promise an actor that does not
5361 // serve; releasing it would lose a name the peer was just told it had. So: claimed, and honest
5462 // that it is not live yet.
55 return { status: 202, body: { username: r.username, generation: r.generation, pending: String(err.message || err) } }
63 // ⛔ THE ACCT TRAVELS WITH A PENDING CLAIM TOO. A peer records the name off `acct` and nothing
64 // else; omitting it here meant the ONE path where the claim succeeded but the actor was not yet
65 // live recorded nothing on the device, while the bridge went on holding the name. The device
66 // then showed "none yet" for ever and could neither re-claim nor release. `pending` already
67 // says it is not servable — that is the honest part; withholding the name is not.
68 return { status: 202, body: { username: r.username, generation: r.generation, acct: acctFor(config, r.username), pending: String(err.message || err) } }
5669 }
5770 }
5871
……@@ -62,7 +75,7 @@ export async function admitClaim ({ registrations, admission, config, onRegister
6275 body: {
6376 username: r.username,
6477 generation: r.generation,
65 acct: host ? r.username + '@' + host : null,
78 acct: acctFor(config, r.username),
6679 actor: `https://${host}/ap/users/${r.username}/actor`,
6780 inbox: `https://${host}/ap/users/${r.username}/inbox`
6881 }
lib/registrations.js +23 -1
……@@ -83,7 +83,29 @@ class BaseRegistrations {
8383 if (typeof root !== 'string' || !HEX64.test(root)) return { ok: false, error: 'identity root required' }
8484
8585 const state = await this._read()
86 if (state.names[username]) return { ok: false, error: 'taken' }
86 const existing = state.names[username]
87 if (existing) {
88 // ⛔ THE HOLDER ASKING AGAIN IS NOT A SQUATTER. Control was proved before this call (admit.js
89 // verifies the chain proof first), so a matching root IS the holder — and answering `taken` to
90 // the person who holds it is how a name becomes unrecoverable. Measured on a handset: the name
91 // was claimed, the device's local record never got written (admit.js's 202 omitted `acct`), and
92 // from then on re-claiming said "taken" while the release page refused to open because it needs
93 // the record that was never written. Held, invisible, and no way back from the device.
94 //
95 // ⛔ THE GENERATION MUST NOT MOVE. It is what makes a REUSED name a different actor, so bumping
96 // it for the same holder would orphan every object id already federated under the old one —
97 // the impersonation-by-name-reuse guard turned on the very person it protects.
98 if (root && existing.root === String(root).toLowerCase()) {
99 // ⚠ The feed key is refreshed, because a person can hold one name across a replaced device
100 // and the bridge must hold the feed it was LAST told to hold.
101 if (feedKey && existing.feedKey !== feedKey) {
102 existing.feedKey = feedKey
103 await this._write(state)
104 }
105 return { ok: true, username: existing.username, generation: existing.generation }
106 }
107 return { ok: false, error: 'taken' }
108 }
87109
88110 if (this.policy !== 'open') {
89111 if (!invite || !state.invites[invite]) return { ok: false, error: 'invite required' }
package.json +2 -2
……@@ -1,7 +1,7 @@
11{
22 "name": "ap-bridge",
3 "version": "0.9.90",
4 "description": "ActivityPub membrane \u2014 a second projection of the self-custody atproto peer",
3 "version": "0.9.91",
4 "description": "ActivityPub membrane — a second projection of the self-custody atproto peer",
55 "type": "module",
66 "main": "index.js",
77 "scripts": {
test/reclaim-and-pending.test.js +115 -0
……@@ -0,0 +1,115 @@
1// Two ways a peer ends up holding a name it cannot see — measured on a handset, both real.
2//
3// ⛔ THE SYMPTOM. `fleeky@ap.7rnx.net` was claimed from the phone on 2026-09-10 18:14:35, is in
4// registrations.json against that device's own feed key, and answers webfinger and its actor URL
5// with 200. The phone's settings page said "1. Your name — none yet", every boot, for a day.
6//
7// The surface records the name only when the bridge NAMES one:
8//
9// if (r.acct) await client.call('set-reach', { fediverse: r.acct }).catch(() => {})
10//
11// and `admitClaim`'s 202 body was `{ username, generation, pending }` — no `acct`. 202 is the path
12// where the name IS claimed but holding the feed threw, so the one case that most needs recording is
13// the one that recorded nothing. A later bridge boot rehydrated the held feed from registrations.json
14// and the actor went live, with the device still knowing nothing about it.
15//
16// ⛔ AND IT COULD NOT BE UNDONE FROM THE DEVICE. Re-claiming returned `taken` — to the holder, whose
17// proof had just been verified — and the release page returns to /settings unless `reach.fediverse`
18// is set, which is the very thing that never got written. Name held, invisible, unreachable: the
19// worst of the three, because everything involved reports success.
20//
21// A name is not the bridge's to withhold from the person who proved they hold it.
22
23import test from 'brittle'
24import b4a from 'b4a'
25import IdentityKey from 'keet-identity-key'
26import crypto from 'hypercore-crypto'
27import { signAttested } from 'favela-identity/lib/attestation.js'
28import { MemoryRegistrations } from '../lib/registrations.js'
29import { Admission } from '../lib/admission.js'
30import { admitClaim } from '../lib/admit.js'
31
32const FEED = 'f'.repeat(64)
33const OTHER_FEED = 'e'.repeat(64)
34
35async function identity () {
36 const idk = await IdentityKey.from({ mnemonic: IdentityKey.generateMnemonic() })
37 const kp = crypto.keyPair()
38 const proof = await IdentityKey.bootstrap({ identity: idk.identityKeyPair }, kp.publicKey)
39 return { rootHex: b4a.toString(idk.identityKeyPair.publicKey, 'hex'), cred: { keyPair: kp, proof: b4a.toString(proof, 'base64') } }
40}
41
42const deps = (extra = {}) => ({
43 registrations: new MemoryRegistrations({ policy: 'open' }),
44 admission: new Admission(),
45 config: { host: 'ap.7rnx.net' },
46 ...extra
47})
48
49async function claim (d, me, username, { feedKey = FEED } = {}) {
50 // ⛔ A FRESH NONCE PER ATTEMPT — it is single-use, so a reused one fails as "unknown or expired
51 // challenge", which reads like a hostile caller rather than a retry.
52 const { nonce } = d.admission.challenge()
53 const proof = signAttested(d.admission.claimBytes({ nonce, username, feedKey }), me.cred)
54 return admitClaim(d, { username, root: me.rootHex, feedKey, nonce, proof })
55}
56
57test('★★ the holder re-claiming their own name gets it back, not "taken"', async (t) => {
58 const d = deps()
59 const me = await identity()
60
61 const first = await claim(d, me, 'alice')
62 t.is(first.status, 200)
63 t.is(first.body.generation, 1)
64
65 // The device lost its local record — the 202 case below is exactly how — and asks again. Its proof
66 // is verified before this point, so the bridge already knows this is the holder.
67 const again = await claim(d, me, 'alice')
68 t.is(again.status, 200, 'the holder is admitted, not refused 409')
69 t.is(again.body.acct, 'alice@ap.7rnx.net', 'and told the name, which is the whole point of asking')
70
71 // ⛔ THE GENERATION MUST NOT MOVE. It is what makes a reused name a DIFFERENT actor: bumping it
72 // here would orphan every object id the live actor has already federated under generation 1.
73 t.is(again.body.generation, 1, 'the same actor, not a new one')
74 t.is((await d.registrations.get('alice')).generation, 1)
75})
76
77test('★★ a re-claim still refuses somebody else', async (t) => {
78 const d = deps()
79 const me = await identity()
80 const stranger = await identity()
81
82 t.is((await claim(d, me, 'alice')).status, 200)
83
84 const theirs = await claim(d, stranger, 'alice')
85 t.is(theirs.status, 409, 'a different root is still taken')
86 t.is(theirs.body.error, 'taken')
87 t.is((await d.registrations.get('alice')).root, me.rootHex, 'and the holder is unchanged')
88})
89
90test('★ re-claiming updates the feed key, because a device can be replaced', async (t) => {
91 const d = deps()
92 const me = await identity()
93
94 await claim(d, me, 'alice')
95 const moved = await claim(d, me, 'alice', { feedKey: OTHER_FEED })
96
97 t.is(moved.status, 200)
98 t.is((await d.registrations.get('alice')).feedKey, OTHER_FEED,
99 'the bridge holds the feed it was last told to hold')
100})
101
102test('★★★ a pending claim still names the acct — the case that recorded nothing', async (t) => {
103 // Holding the feed throws: the name IS claimed, the actor is not servable yet. This is the 202.
104 const d = deps({ onRegistered: async () => { throw new Error('swarm not ready') } })
105 const me = await identity()
106
107 const r = await claim(d, me, 'alice')
108
109 t.is(r.status, 202, 'claimed, and honest that it is not live yet')
110 t.ok(r.body.pending, 'the reason travels with it')
111 t.is(r.body.acct, 'alice@ap.7rnx.net',
112 'and so does the name — a peer records it on acct, so withholding it loses the name silently')
113 t.is(r.body.username, 'alice')
114 t.is(r.body.generation, 1)
115})
test/register-route.test.js +29 -3
……@@ -27,6 +27,22 @@ async function me () {
2727 return ME
2828}
2929
30// ⛔ A SECOND IDENTITY, because `me()` is memoised and one identity cannot express the difference
31// between "the holder asks again" and "somebody else wants this name". The taken-name test below used
32// the same root twice and so asserted the former while describing the latter — which is how refusing
33// the actual holder survived as intended behaviour. Re-claiming by the holder is now admitted (it is
34// the only way back for a device that lost its local record); a stranger is still refused, and that
35// is what this exists to prove.
36async function stranger () {
37 if (STRANGER) return STRANGER
38 const idk = await IdentityKey.from({ mnemonic: IdentityKey.generateMnemonic() })
39 const kp = crypto.keyPair()
40 const proof = await IdentityKey.bootstrap({ identity: idk.identityKeyPair }, kp.publicKey)
41 STRANGER = { rootHex: b4a.toString(idk.identityKeyPair.publicKey, 'hex'), cred: { keyPair: kp, proof: b4a.toString(proof, 'base64') } }
42 return STRANGER
43}
44let STRANGER = null
45
3046const ctxFor = (policy) => ({
3147 config: { host: 'ap.7rnx.net', username: 'primary' },
3248 registrations: new MemoryRegistrations({ policy }),
……@@ -36,8 +52,8 @@ const ctxFor = (policy) => ({
3652const post = (body) => ({ method: 'POST', path: '/ap/register', headers: {}, query: {}, body: JSON.stringify(body) })
3753
3854/** The claim a peer sends: ask for a challenge, sign the exact request, send both. */
39async function claimBody (ctx, { username, feedKey = 'f'.repeat(64), invite = null }) {
40 const who = await me()
55async function claimBody (ctx, { username, feedKey = 'f'.repeat(64), invite = null, as = null }) {
56 const who = as || await me()
4157 const { nonce } = ctx.admission.challenge()
4258 const proof = signAttested(ctx.admission.claimBytes({ nonce, username, feedKey }), who.cred)
4359 const body = { username, root: who.rootHex, feedKey, nonce, proof }
……@@ -82,10 +98,20 @@ test('★ with the invite the operator sent, the same request works', async (t)
8298test('⛔ a taken name is refused, not silently reassigned', async (t) => {
8399 const ctx = ctxFor('open')
84100 await route(post(await claimBody(ctx, { username: 'ashatron' })), ctx)
85 const res = await route(post(await claimBody(ctx, { username: 'ashatron' })), ctx)
101 const res = await route(post(await claimBody(ctx, { username: 'ashatron', as: await stranger() })), ctx)
86102
87103 t.is(res.status, 409)
88104 t.is(JSON.parse(res.body).error, 'taken')
105 t.is((await ctx.registrations.get('ashatron')).root, (await me()).rootHex, 'the holder is unchanged')
106})
107
108test('★ the holder asking again is admitted, which is the only way back from a lost record', async (t) => {
109 const ctx = ctxFor('open')
110 await route(post(await claimBody(ctx, { username: 'ashatron' })), ctx)
111 const res = await route(post(await claimBody(ctx, { username: 'ashatron' })), ctx)
112
113 t.is(res.status, 200)
114 t.is(JSON.parse(res.body).acct, 'ashatron@ap.7rnx.net', 'and told the name it came back for')
89115})
90116
91117test('⛔ a bridge with no registry does not silently accept', async (t) => {