#!/usr/bin/env node import fs from 'fs'; import path from 'path'; const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', '..'); const matrixPath = path.join(repoRoot, 'reports', 'status', 'contract_verification_publish_matrix.json'); const packStatusPath = path.join(repoRoot, 'reports', 'status', 'publication-pack-explorer-status.json'); const outJson = path.join(repoRoot, 'reports', 'status', 'publication-actionable-backlog.json'); const outMd = path.join(repoRoot, 'docs', '11-references', 'PUBLICATION_ACTIONABLE_BACKLOG.md'); const targetChainIds = new Set(['1', '10', '56', '137', '8453']); const nonActionableAutomation = new Set(['inventory-only', 'reference-only']); const autoSubmittableAutomation = new Set(['repo-supported', 'partial']); function ensureDir(filePath) { fs.mkdirSync(path.dirname(filePath), { recursive: true }); } function loadJson(filePath) { return JSON.parse(fs.readFileSync(filePath, 'utf8')); } function byAddress(statusReport) { const index = new Map(); for (const pack of statusReport.packs || []) { for (const entry of pack.entries || []) { index.set(`${entry.chainId}:${entry.address.toLowerCase()}`, entry); } } return index; } function toRow(entry) { return `| ${entry.chainId} | ${entry.chainName} | ${entry.label} | \`${entry.address}\` | ${entry.contractType} | ${entry.automation} | ${entry.explorerStatus} | ${entry.nextAction} |`; } const matrix = loadJson(matrixPath); const packStatus = loadJson(packStatusPath); const statusIndex = byAddress(packStatus); const backlogEntries = matrix.entries .filter((entry) => targetChainIds.has(entry.chainId)) .map((entry) => { const status = statusIndex.get(`${entry.chainId}:${entry.address.toLowerCase()}`) || {}; const explorerStatus = status.explorerStatus || 'not-in-pack-status'; let nextAction = 'No automatic action'; if (nonActionableAutomation.has(entry.automation)) { nextAction = 'Keep inventory/docs aligned; do not treat as repo-owned submission target'; } else if (autoSubmittableAutomation.has(entry.automation)) { nextAction = 'Submit automatically with repo-owned verification flow'; } else if (entry.automation === 'manual-or-external') { nextAction = 'Manual or external closure only; missing repo-supported source bundle or ownership proof'; } return { ...entry, explorerStatus, explorerDetail: status.explorerDetail || null, nextAction, }; }); const autoSubmittable = backlogEntries.filter((entry) => autoSubmittableAutomation.has(entry.automation)); const manualOrExternal = backlogEntries.filter((entry) => entry.automation === 'manual-or-external'); const inventoryOnly = backlogEntries.filter((entry) => nonActionableAutomation.has(entry.automation)); const byChain = new Map(); for (const entry of backlogEntries) { const current = byChain.get(entry.chainId) || { chainId: entry.chainId, chainName: entry.chainName, total: 0, autoSubmittable: 0, manualOrExternal: 0, inventoryOnly: 0, }; current.total += 1; if (autoSubmittableAutomation.has(entry.automation)) current.autoSubmittable += 1; if (entry.automation === 'manual-or-external') current.manualOrExternal += 1; if (nonActionableAutomation.has(entry.automation)) current.inventoryOnly += 1; byChain.set(entry.chainId, current); } const summaryRows = [...byChain.values()] .sort((a, b) => Number(a.chainId) - Number(b.chainId)) .map((chain) => `| ${chain.chainId} | ${chain.chainName} | ${chain.total} | ${chain.autoSubmittable} | ${chain.manualOrExternal} | ${chain.inventoryOnly} |`) .join('\n'); const autoRows = autoSubmittable.length ? autoSubmittable.map(toRow).join('\n') : '| - | - | - | - | - | - | - | Automatic submission backlog is currently empty for the requested packs |'; const manualRows = manualOrExternal.length ? manualOrExternal.map(toRow).join('\n') : '| - | - | - | - | - | - | - | No manual-or-external backlog |'; const payload = { generatedAt: new Date().toISOString(), targetChains: [...targetChainIds], summary: [...byChain.values()].sort((a, b) => Number(a.chainId) - Number(b.chainId)), autoSubmittable, manualOrExternal, inventoryOnlyCount: inventoryOnly.length, }; const md = `# Publication Actionable Backlog **Generated:** ${payload.generatedAt} This artifact separates the five requested publication packs into: - addresses we can honestly treat as **auto-submittable** from the repo - addresses that are **manual or external** - addresses that are only **inventory/reference tracking** ## Chain Summary | Chain ID | Chain | Total Entries | Auto-submittable | Manual / External | Inventory / Reference | | --- | --- | ---: | ---: | ---: | ---: | ${summaryRows} ## Auto-submittable backlog | Chain ID | Chain | Label | Address | Type | Automation | Explorer Status | Next Action | | --- | --- | --- | --- | --- | --- | --- | --- | ${autoRows} ## Manual or external backlog | Chain ID | Chain | Label | Address | Type | Automation | Explorer Status | Next Action | | --- | --- | --- | --- | --- | --- | --- | --- | ${manualRows} ## Closure note For the five requested packs, the repo-owned **automatic** submission pass is complete when the auto-submittable backlog is empty. Any remaining rows in the manual/external table require source provenance, ownership confirmation, or a non-repo verification process before they can be closed honestly. `; ensureDir(outJson); ensureDir(outMd); fs.writeFileSync(outJson, JSON.stringify(payload, null, 2) + '\n'); fs.writeFileSync(outMd, md + '\n'); console.log(`Wrote:\n- ${outJson}\n- ${outMd}`);