Files
proxmox/scripts/status/generate-all-mainnet-readiness.mjs
defiQUG 6db45b4d2b
All checks were successful
Deploy to Phoenix / validate (push) Successful in 1m19s
Deploy to Phoenix / deploy (push) Successful in 52s
Deploy to Phoenix / deploy-atomic-swap-dapp (push) Successful in 2m33s
phoenix-deploy Deployed to cloudflare-sync
Deploy to Phoenix / cloudflare (push) Successful in 39s
Add ALL Mainnet readiness gate generator
2026-04-28 19:38:54 -07:00

389 lines
16 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* Generate ALL Mainnet readiness reports from the canonical matrix and surface.
*
* The reports are intentionally conservative: rows are not promoted by this
* script. It only summarizes what is already proven in config and what remains
* gated by missing addresses, vault assignments, funding, canaries, or protocol
* surface evidence.
*/
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { resolve } from "node:path";
const repoRoot = resolve(new URL("../..", import.meta.url).pathname);
const matrixPath = resolve(repoRoot, "config/all-mainnet-pool-creation-matrix.json");
const surfacePath = resolve(repoRoot, "config/allmainnet-non-dodo-protocol-surface.json");
const outDir = resolve(repoRoot, "reports/status");
const statusesReadyForFunding = new Set(["created", "funded", "live_read", "canary_passed", "production"]);
const statusesReadyForCanary = new Set(["funded", "live_read", "canary_passed", "production"]);
const productionStatuses = new Set(["production"]);
function readJson(path) {
return JSON.parse(readFileSync(path, "utf8"));
}
function countBy(items, keyFn) {
const counts = {};
for (const item of items) {
const key = keyFn(item);
counts[key] = (counts[key] || 0) + 1;
}
return Object.fromEntries(Object.entries(counts).sort(([a], [b]) => a.localeCompare(b)));
}
function tokenRef(token) {
if (!token) return "MISSING";
return `${token.symbol || "MISSING"} ${token.address || "MISSING"}`;
}
function rowRef(row) {
return {
poolId: row.poolId,
chainId: row.chainId,
network: row.network,
protocol: row.protocol,
status: row.status,
requiredForSpend: Boolean(row.requiredForSpend),
publicRoutingEnabled: Boolean(row.publicRoutingEnabled),
baseToken: row.baseToken,
quoteToken: row.quoteToken,
poolAddress: row.poolAddress,
vaultAddress: row.vaultAddress,
missingRequiredVaultRoles: row.missingRequiredVaultRoles || [],
canaryEvidence: row.canaryEvidence,
notes: row.notes || [],
};
}
function rowBlockers(row, surface) {
const blockers = [];
if (row.status === "planned") blockers.push("pool_not_created");
if (!row.baseToken?.address) blockers.push(`missing_base_address:${row.baseToken?.symbol || "unknown"}`);
if (!row.quoteToken?.address) blockers.push(`missing_quote_address:${row.quoteToken?.symbol || "unknown"}`);
if (statusesReadyForFunding.has(row.status) && !row.poolAddress) blockers.push("missing_pool_address");
if ((row.missingRequiredVaultRoles || []).length > 0) blockers.push("missing_required_vault_assignments");
if (row.status === "created") blockers.push("pool_created_but_not_funded");
if (statusesReadyForCanary.has(row.status) && !row.canaryEvidence) blockers.push("missing_canary_evidence");
if (!productionStatuses.has(row.status)) blockers.push("not_production_status");
if (row.chainId === 651940 && surface.summary?.sameChainSwapInventoryPublished !== true) {
blockers.push("all_mainnet_same_chain_inventory_not_published");
}
return blockers;
}
function makeSummary(matrix, surface) {
const rows = matrix.rows;
const requiredRows = rows.filter((row) => row.requiredForSpend === true);
const rowsMissingVaults = rows.filter((row) => (row.missingRequiredVaultRoles || []).length > 0);
const requiredRowsMissingVaults = requiredRows.filter((row) => (row.missingRequiredVaultRoles || []).length > 0);
const rowsMissingTokenAddresses = rows.filter((row) => !row.baseToken?.address || !row.quoteToken?.address);
const requiredRowsMissingTokenAddresses = requiredRows.filter((row) => !row.baseToken?.address || !row.quoteToken?.address);
const rowsMissingCanary = requiredRows.filter((row) => statusesReadyForCanary.has(row.status) && !row.canaryEvidence);
const productionRows = requiredRows.filter((row) => row.status === "production");
return {
generatedAt: new Date().toISOString(),
matrixVersion: matrix.version,
matrixGeneratedAt: matrix.generatedAt,
totalRows: rows.length,
requiredForSpendRows: requiredRows.length,
statusCounts: countBy(rows, (row) => row.status),
protocolCounts: countBy(rows, (row) => row.protocol),
chainStatusCounts: Object.fromEntries(
Object.entries(countBy(rows, (row) => `${row.chainId} ${row.network}`)).sort(([a], [b]) => a.localeCompare(b)),
),
requiredStatusCounts: countBy(requiredRows, (row) => row.status),
plannedRows: rows.filter((row) => row.status === "planned").length,
createdUnfundedRows: rows.filter((row) => row.status === "created").length,
fundedRows: rows.filter((row) => row.status === "funded").length,
liveReadRows: rows.filter((row) => row.status === "live_read").length,
productionRows: productionRows.length,
rowsMissingVaultAssignments: rowsMissingVaults.length,
requiredRowsMissingVaultAssignments: requiredRowsMissingVaults.length,
rowsMissingTokenAddresses: rowsMissingTokenAddresses.length,
requiredRowsMissingTokenAddresses: requiredRowsMissingTokenAddresses.length,
requiredRowsMissingCanaryEvidence: rowsMissingCanary.length,
bridgeOnlyLive: surface.summary?.bridgeOnlyLive === true,
sameChainSwapInventoryPublished: surface.summary?.sameChainSwapInventoryPublished === true,
productionReady:
requiredRows.length > 0 &&
productionRows.length === requiredRows.length &&
requiredRowsMissingVaults.length === 0 &&
rowsMissingCanary.length === 0 &&
surface.summary?.sameChainSwapInventoryPublished === true,
};
}
function makeReadiness(matrix, surface, summary) {
const requiredRows = matrix.rows.filter((row) => row.requiredForSpend === true);
const allBlockers = requiredRows
.map((row) => ({
...rowRef(row),
blockers: rowBlockers(row, surface),
}))
.filter((row) => row.blockers.length > 0);
const protocolSurfaceBlockers = (surface.protocols || [])
.filter((protocol) => protocol.status !== "live" && (!protocol.factoryAddress || !protocol.routerAddress))
.map((protocol) => ({
name: protocol.name,
family: protocol.family,
status: protocol.status,
factoryAddress: protocol.factoryAddress,
routerAddress: protocol.routerAddress,
blockers: [
!protocol.factoryAddress ? "missing_factory_address" : null,
!protocol.routerAddress ? "missing_router_address" : null,
protocol.status !== "live" ? "protocol_surface_not_live" : null,
].filter(Boolean),
}));
return {
generatedAt: summary.generatedAt,
status: summary.productionReady ? "production_ready" : "blocked",
summary,
executionOrder: [
"commit_or_confirm_missing_token_and_accounting_addresses",
"assign_required_vaults_and_pause_controls",
"create_remaining_planned_pools",
"fund_created_and_new_pools",
"run_live_reserve_reads",
"run_bridge_fee_and_destination_settlement_preflights",
"run_10_100_1000_canary_swaps",
"publish_same_chain_inventory_and_verification_artifacts",
"enable_public_routing_only_for_canary_passed_or_production_rows",
],
blockers: allBlockers,
protocolSurfaceBlockers,
externalActionsRequired: [
"operator_signing_required_for_pool_creation_or_funding",
"vault_addresses_required_from_treasury_or_security_owner",
"canary_swap_transactions_required_on_live_networks",
"same_chain_factory_router_pool_inventory_required_before_public_route_generation",
],
};
}
function makeProductionGate(summary, readiness, surface) {
return {
generatedAt: summary.generatedAt,
status: summary.productionReady ? "production_ready" : "blocked",
gates: {
bridgeConfigOk: surface.bridgeSurface?.adapter?.status === "live",
sameChainSwapInventoryPublished: summary.sameChainSwapInventoryPublished,
requiredPoolsCreated: summary.requiredStatusCounts.planned === undefined,
requiredPoolsFundedOrBetter: !["planned", "created"].some((status) => summary.requiredStatusCounts[status] > 0),
vaultAssignmentsComplete: summary.requiredRowsMissingVaultAssignments === 0,
canaryEvidenceComplete: summary.requiredRowsMissingCanaryEvidence === 0,
productionStatusesComplete: summary.productionRows === summary.requiredForSpendRows,
},
counts: summary,
blockers: readiness.blockers.map((row) => ({
poolId: row.poolId,
chainId: row.chainId,
protocol: row.protocol,
status: row.status,
blockers: row.blockers,
})),
};
}
function mdTable(headers, rows) {
const header = `| ${headers.join(" | ")} |`;
const sep = `| ${headers.map((h) => (h.match(/count|rows|chain/i) ? "---:" : "---")).join(" | ")} |`;
const body = rows.map((row) => `| ${row.join(" | ")} |`);
return [header, sep, ...body].join("\n");
}
function escapeCell(value) {
return String(value ?? "-").replace(/\|/g, "\\|").replace(/\n/g, "<br>");
}
function writeReports(matrix, surface, summary, readiness, productionGate) {
mkdirSync(outDir, { recursive: true });
writeFileSync(resolve(outDir, "all-mainnet-deployment-readiness-worklist-latest.json"), `${JSON.stringify(readiness, null, 2)}\n`);
writeFileSync(resolve(outDir, "all-mainnet-production-gate-latest.json"), `${JSON.stringify(productionGate, null, 2)}\n`);
writeFileSync(
resolve(outDir, "all-mainnet-spend-readiness-latest.json"),
`${JSON.stringify(
{
generatedAt: summary.generatedAt,
version: matrix.version,
description: "Derived spend-readiness view for required ALL Mainnet pool rows.",
status: summary.productionReady ? "ready" : "blocked",
summary,
statusCounts: countBy(readiness.blockers, (row) => row.status),
routes: readiness.blockers.map((row) => ({
routeId: row.poolId,
chainId: row.chainId,
network: row.network,
protocol: row.protocol,
status: row.blockers.includes("pool_not_created")
? "missing_pool"
: row.blockers.includes("pool_created_but_not_funded")
? "pool_created_unfunded"
: row.blockers.includes("missing_canary_evidence")
? "missing_canary"
: "blocked",
path: `${row.baseToken?.symbol || "?"} -> ${row.quoteToken?.symbol || "?"}`,
blockers: row.blockers,
})),
assumptions: [
"Spend readiness is blocked until every required pool has vault assignments, funding/reserve evidence, canary evidence, and production status.",
"ALL Mainnet same-chain routing remains blocked while config/allmainnet-non-dodo-protocol-surface.json has sameChainSwapInventoryPublished=false.",
],
},
null,
2,
)}\n`,
);
const poolMatrixReport = {
generatedAt: summary.generatedAt,
summary,
requiredPools: matrix.rows.filter((row) => row.requiredForSpend === true).map(rowRef),
rowsByStatus: countBy(matrix.rows, (row) => row.status),
rowsByProtocol: countBy(matrix.rows, (row) => row.protocol),
};
writeFileSync(resolve(outDir, "all-mainnet-pool-creation-matrix-latest.json"), `${JSON.stringify(poolMatrixReport, null, 2)}\n`);
const summaryRows = [
["totalRows", summary.totalRows],
["requiredForSpendRows", summary.requiredForSpendRows],
["plannedRows", summary.plannedRows],
["createdUnfundedRows", summary.createdUnfundedRows],
["fundedRows", summary.fundedRows],
["liveReadRows", summary.liveReadRows],
["productionRows", summary.productionRows],
["rowsMissingVaultAssignments", summary.rowsMissingVaultAssignments],
["requiredRowsMissingVaultAssignments", summary.requiredRowsMissingVaultAssignments],
["requiredRowsMissingCanaryEvidence", summary.requiredRowsMissingCanaryEvidence],
["sameChainSwapInventoryPublished", summary.sameChainSwapInventoryPublished],
["productionReady", summary.productionReady],
];
const blockersRows = readiness.blockers.map((row) => [
row.chainId,
`\`${escapeCell(row.poolId)}\``,
escapeCell(row.protocol),
escapeCell(row.status),
escapeCell(tokenRef(row.baseToken)),
escapeCell(tokenRef(row.quoteToken)),
escapeCell(row.poolAddress || "-"),
escapeCell(row.blockers.join(", ")),
]);
const readinessMd = [
"# ALL Mainnet Deployment Readiness Worklist",
"",
`Generated: \`${summary.generatedAt}\``,
"",
"## Summary",
"",
mdTable(["Item", "Count"], summaryRows),
"",
"## Execution Order",
"",
...readiness.executionOrder.map((step, index) => `${index + 1}. \`${step}\``),
"",
"## Required Pool Blockers",
"",
blockersRows.length
? mdTable(["Chain", "Pool", "Protocol", "Status", "Base", "Quote", "Pool Address", "Blockers"], blockersRows)
: "No required pool blockers remain.",
"",
"## Protocol Surface Blockers",
"",
readiness.protocolSurfaceBlockers.length
? mdTable(
["Protocol", "Family", "Status", "Factory", "Router", "Blockers"],
readiness.protocolSurfaceBlockers.map((protocol) => [
escapeCell(protocol.name),
escapeCell(protocol.family),
escapeCell(protocol.status),
escapeCell(protocol.factoryAddress || "-"),
escapeCell(protocol.routerAddress || "-"),
escapeCell(protocol.blockers.join(", ")),
]),
)
: "No protocol surface blockers remain.",
"",
].join("\n");
writeFileSync(resolve(outDir, "all-mainnet-deployment-readiness-worklist-latest.md"), readinessMd);
const spendRows = readiness.blockers.map((row) => [
`\`${escapeCell(row.poolId)}\``,
row.chainId,
escapeCell(row.protocol),
escapeCell(`${row.baseToken?.symbol || "?"} -> ${row.quoteToken?.symbol || "?"}`),
escapeCell(
row.blockers.includes("pool_not_created")
? "missing_pool"
: row.blockers.includes("pool_created_but_not_funded")
? "pool_created_unfunded"
: row.blockers.includes("missing_canary_evidence")
? "missing_canary"
: "blocked",
),
escapeCell(row.blockers.join(", ")),
]);
const spendMd = [
"# ALL Mainnet Spend Readiness",
"",
`Generated: \`${summary.generatedAt}\``,
"",
"## Summary",
"",
mdTable(["Item", "Count"], summaryRows),
"",
"## Required Route Gates",
"",
spendRows.length
? mdTable(["Route", "Chain", "Protocol", "Path", "Status", "Blockers"], spendRows)
: "All required spend routes are production-ready.",
"",
"## Direct Mapping Policy",
"",
"Direct ALL Mainnet -> public-network mappings remain inventory-only until verified bridge adapters, fee paths, vault assignments, live reserve reads, and canary evidence are recorded.",
"",
].join("\n");
writeFileSync(resolve(outDir, "all-mainnet-spend-readiness-latest.md"), spendMd);
const requiredPoolRows = poolMatrixReport.requiredPools.map((row) => [
row.chainId,
`\`${escapeCell(row.poolId)}\``,
escapeCell(row.protocol),
escapeCell(row.status),
escapeCell(row.poolAddress || "-"),
escapeCell((row.missingRequiredVaultRoles || []).join(", ") || "-"),
]);
const poolMatrixMd = [
"# ALL Mainnet Pool Creation Matrix",
"",
`Generated: \`${summary.generatedAt}\``,
"",
"## Summary",
"",
mdTable(["Status", "Count"], Object.entries(summary.statusCounts)),
"",
"## Required Pools",
"",
mdTable(["Chain", "Pool", "Protocol", "Status", "Address", "Missing Vault Roles"], requiredPoolRows),
"",
].join("\n");
writeFileSync(resolve(outDir, "all-mainnet-pool-creation-matrix-latest.md"), poolMatrixMd);
}
const matrix = readJson(matrixPath);
const surface = readJson(surfacePath);
const summary = makeSummary(matrix, surface);
const readiness = makeReadiness(matrix, surface, summary);
const productionGate = makeProductionGate(summary, readiness, surface);
writeReports(matrix, surface, summary, readiness, productionGate);
console.log(`[OK] ALL Mainnet readiness reports generated (${readiness.status}).`);