OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 commits / 9bd1ee2

Surface the pear:// link: manifest, RPC, and the releases page
The link existed nowhere queryable. Not in the manifest, not in `pgh deploy log`, and no RPC exposed the seeder despite stats() having been written for exactly that — the only trace was a daemon log line, and once the seeder holds the store there is no way to recover it. Three places now carry it: manifest.json vdrive: { link, key, blocks, locations }, written after a successful publish, non-fatal if it cannot be vdrive-status RPC what this daemon is seeding, optionally by repo releases page the link under a version's artifacts, click to copy The releases page needed no plumbing — ReleaseManager.list() already returns parsed manifests, so v.vdrive was there as soon as the manifest carried it. One correction while wiring the RPC: `anonymous: true` on a handler object does nothing here. Anonymity is an allow-list, ANONYMOUS_METHODS in pear-git.js, so the property would have been silently ignored. It now requires a writer explicitly — the links are public keys and grant nothing alone, but enumerating every repo's drive is a map of what the daemon publishes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
9bd1ee2ff7176f51e0f1052abe7776f3ae0e5996
by plankatron <fleeky@gmail.com> · 2026-08-25T00:48:28.000Z

Changed files (5) +104 -3

lib/handlers/releases.js +21 -0
……@@ -204,6 +204,27 @@ export function releaseHandlers (svc) {
204204 }
205205 },
206206
207 {
208 // What virtual Hyperdrives is this daemon serving, and under what links.
209 //
210 // NOT in ANONYMOUS_METHODS (pear-git.js), so it needs a trusted caller —
211 // UDS operator or a writer. The links themselves are public keys and grant
212 // nothing on their own, but enumerating every repo's drive would hand an
213 // anonymous caller a map of what this daemon publishes, which the repo
214 // listing already gates.
215 name: 'vdrive-status',
216 fn: async (ctx, params = {}) => {
217 await requireWriter(ctx, registry())
218 const seeder = svc._vdriveSeeder
219 if (!seeder) return { enabled: false, drives: [] }
220 const drives = seeder.stats()
221 if (params.repo) {
222 return { enabled: seeder.enabled, drives: drives.filter((d) => d.repo === params.repo) }
223 }
224 return { enabled: seeder.enabled, drives }
225 }
226 },
227
207228 {
208229 // Run the repo's .pear-git/ci.yaml against a ref, collect artifacts,
209230 // publish the resulting release. Returns { ok, version, channel,
lib/vdrive-publish.js +30 -1
……@@ -28,6 +28,33 @@ export function vdriveStoreDir (releaseDir, repo) {
2828 return path.join(releaseDir, repo, '.vdrive')
2929}
3030
31/**
32 * Record the drive in the version's manifest.json.
33 *
34 * The link is otherwise unrecoverable: the manifest never carried it, the
35 * deploy log never printed it, and once the seeder holds the store the only
36 * trace is a daemon log line. Writing it here makes `pgh deploy log` and any
37 * manifest reader able to answer "what is this release's pear:// link".
38 *
39 * Non-fatal: a manifest we cannot annotate must not fail a published release.
40 */
41async function annotateManifest (releaseDir, repo, version, vdrive) {
42 const manifestPath = path.join(releaseDir, repo, 'versions', version, 'manifest.json')
43 try {
44 const raw = JSON.parse(await fsp.readFile(manifestPath, 'utf8'))
45 raw.vdrive = {
46 link: vdrive.link,
47 key: vdrive.key,
48 blocks: vdrive.blocks,
49 locations: vdrive.locations
50 }
51 await fsp.writeFile(manifestPath, JSON.stringify(raw, null, 2))
52 return true
53 } catch {
54 return false
55 }
56}
57
3158async function dirSize (dir) {
3259 let total = 0
3360 for (const e of await fsp.readdir(dir, { withFileTypes: true })) {
……@@ -107,6 +134,8 @@ export async function publishVersionToDrive ({
107134 chunk: chunker
108135 })
109136
137 const annotated = await annotateManifest(releaseDir, repo, version, result)
138
110139 if (onLog) {
111140 // Report the pointer/literal split and the store size. Without them a large
112141 // .vdrive is ambiguous — it reads the same whether the redirect failed (all
……@@ -126,5 +155,5 @@ export async function publishVersionToDrive ({
126155 )
127156 }
128157
129 return { ok: true, ...result }
158 return { ok: true, ...result, annotated }
130159}
lib/web/pages/repo-releases.js +14 -1
……@@ -28,6 +28,9 @@ const RELEASES_STYLES = `
2828 .version-pinned { color: #3fb950; font-size: 0.7rem; font-weight: 500; }
2929 .version-stale { color: #484f58; }
3030 .artifact-list { font-family: 'SF Mono', monospace; font-size: 0.7rem; color: #8b949e; }
31 .vdrive-link { margin-top: 0.35rem; font-family: 'SF Mono', monospace; font-size: 0.68rem; color: #3fb950; cursor: pointer; word-break: break-all; }
32 .vdrive-link:hover { color: #56d364; }
33 .vdrive-label { display: inline-block; background: #1c2128; border: 1px solid #30363d; border-radius: 3px; padding: 0 0.3rem; margin-right: 0.3rem; color: #8b949e; font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.5px; }
3134 .artifact-list a { color: #58a6ff; }
3235</style>`
3336
……@@ -219,6 +222,16 @@ function renderVersionRow (v, pinned, channels, base, repoName, prefix) {
219222 return `<a href="${href}" download>${escapeHtml(a.path)}</a> <span class="meta" style="margin-right:0.5rem">${formatBytes(a.size)}</span>`
220223 }).join(' · ')
221224
225 // A virtual Hyperdrive published alongside the artifacts (ci.yaml `vdrive: true`).
226 // The link is the only durable handle on it — the drive itself is pointers, and
227 // once the daemon seeds it the store is locked — so surface it here, click to copy.
228 const vdriveLink = v.vdrive && v.vdrive.link
229 ? `<div class="vdrive-link" title="Click to copy — pear install ${escapeHtml(v.vdrive.link)}"
230 onclick="navigator.clipboard.writeText('${escapeHtml(v.vdrive.link)}')">
231 <span class="vdrive-label">pear</span> ${escapeHtml(v.vdrive.link)}
232 </div>`
233 : ''
234
222235 const promoteBtn = `
223236 <form action="${base}/releases/promote" method="POST" style="display:inline;margin:0">
224237 <input type="hidden" name="version" value="${escapeHtml(v.version)}">
……@@ -238,7 +251,7 @@ function renderVersionRow (v, pinned, channels, base, repoName, prefix) {
238251 <td><span class="channel-version" style="font-size:0.85rem">${escapeHtml(v.version)}</span> ${isPinned ? '<span class="version-pinned">pinned</span>' : ''}</td>
239252 <td>${timeAgo(v.builtAt)}</td>
240253 <td>${size}</td>
241 <td class="artifact-list">${artifactLinks}</td>
254 <td class="artifact-list">${artifactLinks}${vdriveLink}</td>
242255 <td style="text-align:right;white-space:nowrap">${promoteBtn} ${deleteBtn}</td>
243256 </tr>`
244257}
package.json +1 -1
……@@ -1,6 +1,6 @@
11{
22 "name": "pear-git-v3",
3 "version": "0.1.231",
3 "version": "0.1.232",
44 "description": "P2P git forge \u2014 multi-writer issue/org/release/peer-mirror daemon. Built bottom-up after the failed v2.",
55 "type": "module",
66 "main": "index.js",
test/vdrive-ci.test.js +38 -0
……@@ -125,3 +125,41 @@ async function dirSize (dir) {
125125 }
126126 return total
127127}
128
129test('the drive is recorded in the release manifest', async (t) => {
130 // Without this the link is unrecoverable: it was never in the manifest, never
131 // in `pgh deploy log`, and once the seeder holds the store the only trace is a
132 // daemon log line.
133 const releaseDir = await tmpdir('manifest')
134 const repo = 'demo'
135 const version = '1.0.0'
136 const versionDir = path.join(releaseDir, repo, 'versions', version)
137 await fsp.mkdir(versionDir, { recursive: true })
138 await fsp.writeFile(path.join(versionDir, 'app.bin'), Buffer.alloc(256 * 1024, 4))
139 // a manifest as ReleaseManager would have written it
140 await fsp.writeFile(
141 path.join(versionDir, 'manifest.json'),
142 JSON.stringify({ version, builtAt: Date.now(), artifacts: [] }, null, 2)
143 )
144
145 const { chunk } = await import('hyper-overlay/chunker')
146 const res = await publishVersionToDrive({
147 releaseDir,
148 repo,
149 version,
150 channel: 'production',
151 vdrive: { enabled: true, channels: null },
152 chunk
153 })
154
155 t.is(res.ok, true)
156 t.is(res.annotated, true, 'manifest was annotated')
157
158 const manifest = JSON.parse(await fsp.readFile(path.join(versionDir, 'manifest.json'), 'utf8'))
159 t.ok(manifest.vdrive, 'manifest carries a vdrive block')
160 t.is(manifest.vdrive.link, res.link, 'and the link matches what was published')
161 t.ok(manifest.vdrive.key && manifest.vdrive.blocks > 0, 'with key and block count')
162 t.ok(manifest.version === version, 'existing manifest fields survive')
163
164 await fsp.rm(releaseDir, { recursive: true, force: true })
165})