OverviewFilesCommitsIssuesDocsReleasesPeers

pgh-sidecar commits / 69b0d16

0.2.54: update-name RPC, and sign the name ops
Adds the `update-name` handler that #143 needs — resolve, check the caller's identityRoot actually owns the claim, then delegate to NameRegistry with a signer. The ownership check is at the call site on purpose: an op apply drops is indistinguishable from replication lag, so the caller gets a real error naming both roots instead of silence. `release-name` now signs too. Both go through `_nameSigner()`, which builds { identityRoot, devicePubkey, deviceSecretKey, chain } from the loaded identity. An identity with no chain — the raw/legacy fallback, which has no attestation to offer — degrades to the legacy string form so it keeps behaving exactly as before rather than throwing. Needs pgh 0.1.141 for the signed-op support. Forge org #143. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
69b0d165c28dc501eb634bbf8fc5bdff0735cbcd
by plankatron <fleeky@gmail.com> · 2026-08-25T14:33:13.000Z

Changed files (2) +51 -5

lib/index.js +50 -4
……@@ -1579,6 +1579,23 @@ export class Sidecar {
15791579 replicas: this._nameReplicas.map(r => ({ keyHex: r.keyHex, writable: r.registry.writable }))
15801580 }))
15811581
1582 // The identity acting on a name claim, in the shape NameRegistry's write
1583 // methods take. A signed (v2) op needs the device secret AND the keet
1584 // attestation chain; an identity with neither — the raw/legacy fallback,
1585 // which has no chain to offer — degrades to the legacy string form so it
1586 // keeps working exactly as before rather than throwing. See org #143.
1587 this._nameSigner = () => {
1588 const id = this._identity
1589 const root = identityRootHex(id)
1590 if (!id || !id.chain || !id.secretKey) return root
1591 return {
1592 identityRoot: root,
1593 devicePubkey: id.publicKeyHex,
1594 deviceSecretKey: id.secretKey,
1595 chain: id.chain
1596 }
1597 }
1598
15821599 svc.handle('claim-name', async (ctx, params) => {
15831600 if (!params || !params.name) throw new Error('claim-name requires { name }')
15841601 if (!this._nameRegistry.writable) {
……@@ -1608,13 +1625,42 @@ export class Sidecar {
16081625
16091626 svc.handle('release-name', async (ctx, params) => {
16101627 if (!params || !params.name) throw new Error('release-name requires { name }')
1611 // Same anchor the claim was written with (org #142) — a release that
1612 // computes it differently from the claim can never match.
1613 const identityRoot = identityRootHex(this._identity)
1614 await this._nameRegistry.release(params.name, identityRoot)
1628 await this._nameRegistry.release(params.name, this._nameSigner())
16151629 return { ok: true, released: params.name }
16161630 })
16171631
1632 svc.handle('update-name', async (ctx, params) => {
1633 if (!params || !params.name) throw new Error('update-name requires { name }')
1634 if (!this._nameRegistry.writable) {
1635 throw new Error('name registry is not writable on this sidecar (join via invite first)')
1636 }
1637 const existing = await this._nameRegistry.resolve(params.name)
1638 if (!existing) throw new Error('name not claimed: ' + params.name)
1639
1640 // Fail here rather than appending an op apply will drop — a dropped op
1641 // is indistinguishable from replication lag at the call site.
1642 const signer = this._nameSigner()
1643 const mine = typeof signer === 'string' ? signer : signer.identityRoot
1644 if (existing.publisher.identityRoot !== mine) {
1645 throw new Error(
1646 'name "' + params.name + '" is owned by ' +
1647 String(existing.publisher.identityRoot).slice(0, 12) + '… — this identity is ' +
1648 String(mine).slice(0, 12) + '…'
1649 )
1650 }
1651
1652 const updates = {}
1653 if (params.registryKey) updates.registryKey = params.registryKey
1654 if (params.serverKey) updates.serverKey = params.serverKey
1655 if (params.description !== undefined) updates.description = params.description
1656 if (Object.keys(updates).length === 0) {
1657 throw new Error('update-name requires at least one of { registryKey, serverKey, description }')
1658 }
1659
1660 await this._nameRegistry.updateName(params.name, updates, signer)
1661 return await this._nameRegistry.resolve(params.name)
1662 })
1663
16181664 svc.handle('list-names', async (ctx, params) => {
16191665 // Merge own registry + subscribed replicas, dedup by name (own wins).
16201666 const seen = new Map()
package.json +1 -1
……@@ -1,6 +1,6 @@
11{
22 "name": "pgh-sidecar",
3 "version": "0.2.53",
3 "version": "0.2.54",
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",