Add ALL Mainnet readiness gate generator
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
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
This commit is contained in:
@@ -45,7 +45,8 @@
|
||||
"mission-control:start": "pnpm --filter mission-control start",
|
||||
"mission-control:test": "pnpm --filter mission-control test",
|
||||
"token-lists:validate-metadata": "node scripts/validation/validate-token-list-metadata.mjs",
|
||||
"pool-matrix:validate": "node scripts/validation/validate-pool-creation-matrix.mjs"
|
||||
"pool-matrix:validate": "node scripts/validation/validate-pool-creation-matrix.mjs",
|
||||
"all-mainnet:readiness": "node scripts/status/generate-all-mainnet-readiness.mjs"
|
||||
},
|
||||
"keywords": [
|
||||
"proxmox",
|
||||
|
||||
388
scripts/status/generate-all-mainnet-readiness.mjs
Executable file
388
scripts/status/generate-all-mainnet-readiness.mjs
Executable file
@@ -0,0 +1,388 @@
|
||||
#!/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}).`);
|
||||
@@ -22,8 +22,38 @@ jq -e '
|
||||
and (.summary | type == "object")
|
||||
and (.summary.bridgeOnlyLive | type == "boolean")
|
||||
and (.summary.sameChainSwapInventoryPublished | type == "boolean")
|
||||
and (.classificationFramework.metadataDomains | type == "array")
|
||||
and ((.classificationFramework.metadataDomains - [
|
||||
"backingMetadata",
|
||||
"bridgeMetadata",
|
||||
"cashMetadata",
|
||||
"commodityMetadata",
|
||||
"reserveMetadata",
|
||||
"securityMetadata",
|
||||
"settlementMetadata"
|
||||
]) | length == 0)
|
||||
and (.documentedTokens | type == "array")
|
||||
and ((.documentedTokens | length) > 0)
|
||||
and all(.documentedTokens[]; . as $token | (
|
||||
(.symbol | type == "string")
|
||||
and (.address | test("^0x[0-9a-fA-F]{40}$"))
|
||||
and (.category | type == "string")
|
||||
and (.instrumentType | type == "string")
|
||||
and (.backingAssets | type == "array")
|
||||
and (.tags | type == "array")
|
||||
and (.backingMetadata | type == "object")
|
||||
and (.bridgeMetadata | type == "object")
|
||||
and (.cashMetadata | type == "object")
|
||||
and (.commodityMetadata | type == "object")
|
||||
and (.reserveMetadata | type == "object")
|
||||
and (.securityMetadata | type == "object")
|
||||
and (.settlementMetadata | type == "object")
|
||||
and (($token.gruVersion == null) or (($token.tags | index("gru:" + $token.gruVersion)) != null))
|
||||
))
|
||||
and (.bridgeSurface.adapter.address | test("^0x[0-9a-fA-F]{40}$"))
|
||||
and (.bridgeSurface.adapter.status == "live")
|
||||
' "$F" >/dev/null || {
|
||||
echo "[ERROR] allmainnet-non-dodo-protocol-surface.json: expected chainId 651940, status string, summary.bridgeOnlyLive and summary.sameChainSwapInventoryPublished booleans"
|
||||
echo "[ERROR] allmainnet-non-dodo-protocol-surface.json: expected chainId 651940, summary flags, metadata domains, documented token metadata, GRU version tags, and live bridge adapter"
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user