🌳
pt0/ipfsF/verifyIpfsPinsAI.mts
1import { getPinTargets, type PinTarget } from './ipfsClientAI.mts'
6const isPinnedOn = async (client: PinTarget['client'], cid: string): Promise<boolean> => {
7 try {
8 // @ts-expect-error kubo-rpc-client returns AsyncIterable with .next()
9 await client.pin.ls({paths: [cid]}).next()
10 return true
11 } catch { return false }
14export const verifyIpfsPins = async ({rootCids}: {rootCids: MfsRootCidEntry[]}) => {
15 const targets = await getPinTargets()
16 const result = {
17 totalRoots: rootCids.length,
18 hosts: targets.map(t => t.host),
19 pinnedOnAll: 0,
20 missing: 0,
21 healed: 0,
22 perRoot: {} as Record<string, {cid: string, pinnedOn: string[], missingOn: string[], healed: string[]}>,
23 }
25 for (const {label, cid} of rootCids) {
26 const entry = {cid, pinnedOn: [] as string[], missingOn: [] as string[], healed: [] as string[]}
27 result.perRoot[label] = entry
29 for (const t of targets) {
30 if (await isPinnedOn(t.client, cid)) { entry.pinnedOn.push(t.host); continue }
31 entry.missingOn.push(t.host)
32 try {
33 await t.client.pin.add(cid)
34 entry.healed.push(t.host)
35 result.healed++
36 betLog('pinHeal', {label, cid, host: t.host})
37 } catch (err) {
38 betLog('pinHealFail', {label, cid, host: t.host, errMsg: (err as Error).message})
39 }
40 }
42 if (entry.missingOn.length === 0) result.pinnedOnAll++
43 else if (entry.pinnedOn.length === 0) result.missing++
44 }
46 const failedHeals = Object.values(result.perRoot).filter(e =>
47 e.missingOn.length > 0 && e.healed.length < e.missingOn.length
48 ).length
49 if (failedHeals > 0) await noThrowNotifErr('ipfsMfsRootMissing', result)
50 betLog('ipfsPinVerifyResult', result)
51 return result