OverviewFilesCommitsIssuesDocsReleasesPeers

pgh-sidecar commits / ddbe9e0

0.2.57: reanchor-name accepts force
Skips the alreadyAnchored early return so the op is re-emitted for peers whose view is behind (org #142). The ownership gate now accepts a claim anchored to EITHER this identity's root or its device key — the first is the already-done case, the second is the repair case — so a claim belonging to another identity is still refused outright. The post-append assertion only fires when this peer was the one that needed the change; under force the local view legitimately stays put. Needs pgh 0.1.145. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ddbe9e025f689ccea5c866b1030fbe688af6f37c
by plankatron <fleeky@gmail.com> · 2026-08-25T18:26:37.000Z

Changed files (2) +23 -10

lib/index.js +22 -9
……@@ -1640,23 +1640,36 @@ export class Sidecar {
16401640 if (typeof signer === 'string') {
16411641 throw new Error('this identity has no attestation chain, so it cannot prove which root owns the name')
16421642 }
1643 if (existing.publisher.identityRoot === signer.identityRoot) {
1644 return { ok: true, alreadyAnchored: true, entry: existing }
1645 }
1646 if (existing.publisher.identityRoot !== signer.devicePubkey) {
1643 const anchor = existing.publisher.identityRoot
1644 if (anchor !== signer.identityRoot && anchor !== signer.devicePubkey) {
16471645 throw new Error(
1648 'name "' + params.name + '" is anchored to ' +
1649 String(existing.publisher.identityRoot).slice(0, 12) + '… — this device is ' +
1650 String(signer.devicePubkey).slice(0, 12) + '…; only the anchoring key can re-anchor'
1646 'name "' + params.name + '" is anchored to ' + String(anchor).slice(0, 12) +
1647 '… — this device is ' + String(signer.devicePubkey).slice(0, 12) +
1648 '…; only the anchoring key can re-anchor'
16511649 )
16521650 }
16531651
1652 // `force` re-emits the op even when THIS peer's view already shows the
1653 // claim re-anchored. It exists because a peer running code without an
1654 // OP_REANCHOR_NAME branch consumes the op and advances past it — a new
1655 // op type does not degrade gracefully the way a new field does, and
1656 // upgrading afterwards does not re-run history. A fresh op re-applies on
1657 // peers still showing the device anchor and is skipped by peers already
1658 // on the root, since apply tests `claim.identityRoot === op.devicePubkey`
1659 // against each peer's OWN view. Idempotent by construction.
1660 if (anchor === signer.identityRoot && !params.force) {
1661 return { ok: true, alreadyAnchored: true, entry: existing }
1662 }
1663
16541664 await this._nameRegistry.reanchor(params.name, signer)
16551665 const entry = await this._nameRegistry.resolve(params.name)
1656 if (entry.publisher.identityRoot !== signer.identityRoot) {
1666 // Under force the local view legitimately does not change — the op is
1667 // for peers that are behind — so only assert progress when this peer was
1668 // the one that needed it.
1669 if (anchor === signer.devicePubkey && entry.publisher.identityRoot !== signer.identityRoot) {
16571670 throw new Error('re-anchor did not apply for "' + params.name + '" (op refused)')
16581671 }
1659 return { ok: true, entry }
1672 return { ok: true, forced: !!params.force, entry }
16601673 })
16611674
16621675 svc.handle('release-name', async (ctx, params) => {
package.json +1 -1
……@@ -1,6 +1,6 @@
11{
22 "name": "pgh-sidecar",
3 "version": "0.2.56",
3 "version": "0.2.57",
44 "description": "Small optional user-level daemon for pgh \u2014 shared identity vault, OTA polling, device linking, bootstrap seed, NameRegistry host",
55 "type": "module",
66 "main": "lib/index.js",