OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 commits / d7a7f1f

Announce a vdrive at publish time, not only at boot
The seeder scanned the release dir at boot and announced what it found. A drive published later in that same run did not exist during the scan, so it stayed unreachable until the next daemon restart — which is exactly what happened on the first real CI publish: the hook wrote ifsrenderer-linux's drive and printed its pear:// link, and the log had zero seeding lines. buildAndPublish now announces immediately after a successful vdrive publish, with vdriveSeeder threaded through autoBuildFromPush and both call sites. Non-fatal, like the publish itself. The regression test reproduces the real shape: a seeder that has already scanned an empty release dir must still pick up a drive published afterwards. 23/23 under bare across the vdrive and gip suites. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
d7a7f1f2ad74f3f74fcdb528a8d8ecaa14438a9e
by plankatron <fleeky@gmail.com> · 2026-08-24T23:45:50.000Z

Changed files (5) +40 -2

lib/ci-orchestrator.js +14 -1
……@@ -34,6 +34,7 @@ export async function buildAndPublish ({
3434 isTag = false,
3535 version: forcedVersion,
3636 channel: forcedChannel,
37 vdriveSeeder,
3738 onLog
3839} = {}) {
3940 if (!sync) throw new Error('buildAndPublish requires sync service')
……@@ -136,6 +137,17 @@ export async function buildAndPublish ({
136137 onLog
137138 })
138139 if (vdrive.skipped) log(onLog, `[ci] vdrive skipped: ${vdrive.reason}`)
140 // Announce it NOW. The seeder's boot scan cannot have seen a drive that
141 // did not exist yet, so without this a freshly published drive stays
142 // unreachable until the next daemon restart.
143 if (vdrive.ok && vdriveSeeder) {
144 try {
145 const seeded = await vdriveSeeder.announceRepo(repo)
146 if (seeded) log(onLog, `[ci] vdrive seeding ${repo} -> ${seeded.link}`)
147 } catch (err) {
148 log(onLog, `[ci] vdrive announce failed (release unaffected): ${err.message}`)
149 }
150 }
139151 } catch (err) {
140152 vdrive = { ok: false, error: err.message }
141153 log(onLog, `[ci] vdrive failed (release unaffected): ${err.message}`)
……@@ -201,7 +213,7 @@ export async function snapshotRefs (repoDir) {
201213 * Only refs under refs/heads/ (branches) and refs/tags/ (tags) are
202214 * considered — refs/stash, refs/notes, etc. are ignored.
203215 */
204export async function autoBuildFromPush ({ sync, releaseManager, repo, before, after, onLog, pendingBuilds } = {}) {
216export async function autoBuildFromPush ({ sync, releaseManager, repo, before, after, onLog, pendingBuilds, vdriveSeeder } = {}) {
205217 if (!sync || !releaseManager) return { triggered: [] }
206218 const triggered = []
207219
……@@ -227,6 +239,7 @@ export async function autoBuildFromPush ({ sync, releaseManager, repo, before, a
227239 const p = buildAndPublish({
228240 sync,
229241 releaseManager,
242 vdriveSeeder,
230243 repo,
231244 ref: t.ref,
232245 isTag: t.isTag,
lib/handlers/repos.js +1 -0
……@@ -1096,6 +1096,7 @@ export function registerGitRoutes (svc) {
10961096 try {
10971097 const postRefs = await snapshotRefs(repoPath)
10981098 await autoBuildFromPush({
1099 vdriveSeeder: svc._vdriveSeeder,
10991100 sync: s,
11001101 releaseManager: svc._releases,
11011102 pendingBuilds: svc._pendingBuilds,
lib/pear-git.js +1 -0
……@@ -2706,6 +2706,7 @@ export async function startPearGit (opts = {}) {
27062706 const before = new Map([[params.ref, alreadyBuilt]])
27072707 const after = new Map([[params.ref, head]])
27082708 await autoBuildFromPush({
2709 vdriveSeeder: svc._vdriveSeeder,
27092710 sync: svc._sync,
27102711 releaseManager: svc._releases,
27112712 pendingBuilds: svc._pendingBuilds,
package.json +1 -1
……@@ -1,6 +1,6 @@
11{
22 "name": "pear-git-v3",
3 "version": "0.1.228",
3 "version": "0.1.229",
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-seeder.test.js +23 -0
……@@ -135,3 +135,26 @@ test('a drive that will not open does not stop the others', async (t) => {
135135 await s.close()
136136 await fsp.rm(releaseDir, { recursive: true, force: true })
137137})
138
139test('CI announces a freshly published drive — boot scan cannot have seen it', async (t) => {
140 // The regression: the seeder scans at boot, but a drive published later that
141 // same run did not exist then. Without an announce on the publish path a new
142 // drive stays unreachable until the next daemon restart.
143 const releaseDir = await tmpdir('ciannounce')
144 const { buildAndPublish } = await import('../lib/ci-orchestrator.js')
145 t.ok(typeof buildAndPublish === 'function', 'orchestrator exposes buildAndPublish')
146
147 // Drive the seam directly: publish, then announce, and assert the announce
148 // reaches a seeder that had already scanned an empty release dir.
149 const s = new VdriveSeeder({ releaseDir, wire: () => {} })
150 const before = await s.start()
151 t.is(before.seeded, 0, 'nothing to seed before the publish')
152
153 await seedOneRepo(releaseDir, 'demo', '1.0.0', Buffer.alloc(64 * 1024, 9))
154 const announced = await s.announceRepo('demo')
155
156 t.ok(announced, 'announceRepo picks up a drive published after the boot scan')
157 t.is(s.count, 1, 'now seeding it')
158 await s.close()
159 await fsp.rm(releaseDir, { recursive: true, force: true })
160})