/**
* The release index must describe the version directory as it ACTUALLY IS
* after the vdrive step, not as it was before.
*
* ci-orchestrator step 7 publishes via ReleaseManager, which writes _index.json
* and hashes manifest.json. Step 8 then publishes the vdrive, and
* vdrive-publish.js:annotateManifest() appends the `vdrive` block to that same
* manifest while copyLocations() drops locations.json beside it. Nothing
* re-indexed, so the index advertised a content hash for bytes that no longer
* existed.
*
* Measured in prod 2026-09-05 (ifsrenderer-linux linux-port-52962c7):
*
* 14:50:30.106 _index.json written -> manifest.json @ 665 bytes
* 14:50:39.166 manifest.json rewritten -> 880 bytes
*
* The mirror fetched 5 of 6 files, failed on the manifest, and without it the
* version reads `incomplete` on the far side: rendered "— no files", never
* re-served, channel pin stranded on the previous build. See pear-git-v3 #18.
*/
import test from 'brittle'
import { mkdtemp, readFile, writeFile, stat } from '#node:fs/promises'
import { join } from '#node:path'
import { tmpdir } from '#node:os'
import { hashChunk } from 'hyper-overlay/chunker'
import { ReleaseManager, VDRIVE_LOCATIONS_FILE } from '../lib/release-manager.js'
const REPO = 'demo-repo'
const VERSION = 'linux-port-deadbee'
async function published () {
const dir = await mkdtemp(join(tmpdir(), 'pgv3-relidx-'))
const rm = new ReleaseManager({ releaseDir: dir })
await rm.publish({
repo: REPO,
version: VERSION,
channel: 'experimental',
artifacts: [{ path: 'dist/app.AppImage', data: Buffer.from('an artifact') }]
})
return { dir, rm }
}
/** What the vdrive step does to a version directory AFTER it was published. */
async function runVdriveStep (dir) {
const vdir = join(dir, REPO, 'versions', VERSION)
const manifestPath = join(vdir, 'manifest.json')
const raw = JSON.parse(await readFile(manifestPath, 'utf8'))
raw.vdrive = { link: 'pear://xyzzy', key: 'ab'.repeat(32), blocks: 1839, locations: 1217 }
await writeFile(manifestPath, JSON.stringify(raw, null, 2))
await writeFile(join(vdir, VDRIVE_LOCATIONS_FILE), JSON.stringify({ 0: 'x' }))
}
const indexed = async (dir) =>
JSON.parse(await readFile(join(dir, '_index.json'), 'utf8')).files || []
test('the index describes the manifest as it is on disk after the vdrive step', async (t) => {
const { dir, rm } = await published()
await runVdriveStep(dir)
await rm.reannounce(REPO, VERSION, 'experimental')
const rel = REPO + '/versions/' + VERSION + '/manifest.json'
const entry = (await indexed(dir)).find(f => f.path === rel)
t.ok(entry, 'manifest.json is indexed')
const abs = join(dir, rel)
t.is(entry.size, (await stat(abs)).size,
'indexed size matches disk — a stale size means a mirror fetches a hash nothing holds')
t.is(entry.hash, hashChunk(await readFile(abs)), 'indexed hash matches disk')
})
test('locations.json is indexed, so the vdrive index can actually replicate', async (t) => {
const { dir, rm } = await published()
await runVdriveStep(dir)
await rm.reannounce(REPO, VERSION, 'experimental')
const rel = REPO + '/versions/' + VERSION + '/' + VDRIVE_LOCATIONS_FILE
const entry = (await indexed(dir)).find(f => f.path === rel)
// Copied into the version dir explicitly "so it replicates". Without an index
// entry a mirror holds every byte and the vdrive key yet cannot resolve a
// single block — the exact failure that copy exists to prevent.
t.ok(entry, 'locations.json is in the index')
if (entry) t.is(entry.hash, hashChunk(await readFile(join(dir, rel))), 'and its hash matches')
})
test('a version with no vdrive is unaffected — the sidecar is simply absent', async (t) => {
const { dir, rm } = await published()
const files = await indexed(dir)
const rel = REPO + '/versions/' + VERSION + '/' + VDRIVE_LOCATIONS_FILE
t.absent(files.find(f => f.path === rel), 'no phantom entry for a file that does not exist')
t.ok(files.find(f => f.path.endsWith('/manifest.json')), 'manifest still indexed')
})