| … | … | @@ -1640,23 +1640,36 @@ export class Sidecar { |
| 1640 | 1640 | if (typeof signer === 'string') { |
| 1641 | 1641 | throw new Error('this identity has no attestation chain, so it cannot prove which root owns the name') |
| 1642 | 1642 | } |
| 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) { |
| 1647 | 1645 | 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' |
| 1651 | 1649 | ) |
| 1652 | 1650 | } |
| 1653 | 1651 | |
| 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 | |
| 1654 | 1664 | await this._nameRegistry.reanchor(params.name, signer) |
| 1655 | 1665 | 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) { |
| 1657 | 1670 | throw new Error('re-anchor did not apply for "' + params.name + '" (op refused)') |
| 1658 | 1671 | } |
| 1659 | | return { ok: true, entry } |
| 1672 | return { ok: true, forced: !!params.force, entry } |
| 1660 | 1673 | }) |
| 1661 | 1674 | |
| 1662 | 1675 | svc.handle('release-name', async (ctx, params) => { |