fix: align deployment manifest schema

This commit is contained in:
golem
2026-08-21 02:55:41 -06:00
parent be8f01ea4e
commit c6bf8a683f
8 changed files with 389 additions and 139 deletions
+62 -17
View File
@@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import test from "node:test";
import { atomicWrite, finalizeDeployment, preflightDeploy, runFinalizeCli } from "./finalize-manifest.mjs";
import { atomicWrite, finalizeDeployment, preflightDeploy, runFinalizeCli, validateManifest } from "./finalize-manifest.mjs";
import { runSelectCli, selectManifest } from "./select-manifest.mjs";
const TOKEN = "0x1000000000000000000000000000000000000001";
@@ -21,9 +21,9 @@ test("preflight rejects an existing target canonical manifest with the safe reco
() => preflightDeploy({ root, network: "anvil" }),
/make reset-local/
);
await writeJson(join(root, "deployments", "base-sepolia.json"), manifest({ network: "base-sepolia", chainId: 84532 }));
await writeJson(join(root, "deployments", "base-sepolia.json"), manifest({ network: "baseSepolia", chainId: 84532 }));
await assert.rejects(
() => preflightDeploy({ root, network: "base-sepolia" }),
() => preflightDeploy({ root, network: "baseSepolia" }),
/make archive-base-manifest/
);
});
@@ -42,17 +42,38 @@ test("direct manifest CLIs print the exact educational warning", async () => {
});
});
test("finalizer confirms a nested public actor manifest and preserves omitted Base URLs", async () => {
await withFixture(async (root) => {
const pending = manifest({ network: "baseSepolia", chainId: 84532, deploymentBlock: 0 });
await writeJson(join(root, "deployments", "pending.json"), pending);
await writeBroadcast(root, pending);
const output = await finalizeDeployment({ root, rpc: fakeRpc({ chainId: "0x14a34" }) });
assert.deepEqual(output.manifest, { ...pending, deploymentBlock: 42 });
assert.equal(Object.hasOwn(output.manifest, "rpcUrl"), false);
assert.equal(Object.hasOwn(output.manifest, "explorerBaseUrl"), false);
assert.deepEqual(output.manifest.actors, [{ label: "owner", address: OWNER }]);
});
});
test("validator rejects the legacy parallel actor and explorer schema", () => {
assert.throws(
() => validateManifest(legacyManifest()),
/unknown field|missing required field/
);
});
test("finalizer writes only a receipt-confirmed manifest and preserves active until selection", async () => {
await withFixture(async (root) => {
const pending = manifest({ deploymentBlock: 0 });
await writeJson(join(root, "deployments", "pending.json"), pending);
await writeJson(join(root, "deployments", "active.json"), manifest({ network: "base-sepolia", chainId: 84532, deploymentBlock: 88 }));
await writeJson(join(root, "deployments", "active.json"), manifest({ network: "baseSepolia", chainId: 84532, deploymentBlock: 88 }));
await writeBroadcast(root, pending, { hash: "0xaaa", contractAddress: PROXY });
const output = await finalizeDeployment({ root, rpc: fakeRpc() });
assert.equal(output.path, join(root, "deployments", "anvil.json"));
assert.deepEqual(await readJson(output.path), { ...pending, deploymentBlock: 42 });
assert.deepEqual(await readJson(join(root, "deployments", "active.json")), manifest({ network: "base-sepolia", chainId: 84532, deploymentBlock: 88 }));
assert.deepEqual(await readJson(join(root, "deployments", "active.json")), manifest({ network: "baseSepolia", chainId: 84532, deploymentBlock: 88 }));
});
});
@@ -67,9 +88,9 @@ test("finalizer rejects invalid receipt, transaction, RPC, code, slot, and secre
["missing code", { rpc: fakeRpc({ missingCode: TOKEN }) }, /has no code/],
["implementation slot mismatch", { rpc: fakeRpc({ slot: TOKEN }) }, /implementation slot/],
["secret-bearing pending manifest", { pending: manifest({ rpcUrl: "https://user:password@example.invalid" }) }, /credential|secret|endpoints/i],
["mnemonic in actor label", { pending: manifest({ deploymentBlock: 0, actorLabels: ["owner", "test test test test test test test test test test test junk", "Bob"] }) }, /prohibited/i],
["private key in actor label", { pending: manifest({ deploymentBlock: 0, actorLabels: ["owner", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", "Bob"] }) }, /prohibited/i],
["credential in RPC path", { pending: manifest({ deploymentBlock: 0, rpcUrl: "https://sepolia.base.org/v1/secret-token" }) }, /public endpoint|prohibited/i],
["mnemonic in actor label", { pending: manifest({ deploymentBlock: 0, actors: localActors("test test test test test test test test test test test junk") }) }, /prohibited/i],
["private key in actor label", { pending: manifest({ deploymentBlock: 0, actors: localActors("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80") }) }, /prohibited/i],
["credential in RPC path", { pending: manifest({ network: "baseSepolia", chainId: 84532, deploymentBlock: 0, rpcUrl: "https://sepolia.base.org/v1/secret-token" }) }, /public URL|prohibited/i],
["unknown manifest field", { pending: manifest({ deploymentBlock: 0, harmlessLookingField: "not allowed" }) }, /unknown field/i],
];
@@ -92,7 +113,7 @@ test("finalizer rejects invalid receipt, transaction, RPC, code, slot, and secre
test("finalizer failure leaves an initially absent canonical manifest absent", async () => {
await withFixture(async (root) => {
const pending = manifest({ deploymentBlock: 0, actorLabels: ["owner", "MNEMONIC", "Bob"] });
const pending = manifest({ deploymentBlock: 0, actors: localActors("MNEMONIC") });
await writeJson(join(root, "deployments", "pending.json"), pending);
await writeJson(join(root, "deployments", "active.json"), manifest({ deploymentBlock: 8 }));
await writeBroadcast(root, pending);
@@ -105,23 +126,23 @@ test("finalizer failure leaves an initially absent canonical manifest absent", a
test("selection atomically replaces active with only a valid named canonical manifest", async () => {
await withFixture(async (root) => {
const anvil = manifest({ deploymentBlock: 31 });
const base = manifest({ network: "base-sepolia", chainId: 84532, deploymentBlock: 32 });
const base = manifest({ network: "baseSepolia", chainId: 84532, deploymentBlock: 32 });
await writeJson(join(root, "deployments", "anvil.json"), anvil);
await writeJson(join(root, "deployments", "base-sepolia.json"), base);
await selectManifest({ root, network: "anvil" });
const anvilBytes = await readFile(join(root, "deployments", "anvil.json"));
assert.deepEqual(await readFile(join(root, "deployments", "active.json")), anvilBytes);
await selectManifest({ root, network: "base-sepolia" });
await selectManifest({ root, network: "baseSepolia" });
assert.deepEqual(await readFile(join(root, "deployments", "anvil.json")), anvilBytes);
assert.deepEqual(await readJson(join(root, "deployments", "active.json")), base);
const baseBytes = await readFile(join(root, "deployments", "base-sepolia.json"));
await selectManifest({ root, network: "anvil" });
assert.deepEqual(await readFile(join(root, "deployments", "base-sepolia.json")), baseBytes);
await writeJson(join(root, "deployments", "base-sepolia.json"), manifest({ network: "base-sepolia", chainId: 84532, deploymentBlock: 0 }));
await writeJson(join(root, "deployments", "base-sepolia.json"), manifest({ network: "baseSepolia", chainId: 84532, deploymentBlock: 0 }));
const beforeActive = await readFile(join(root, "deployments", "active.json"));
await assert.rejects(() => selectManifest({ root, network: "base-sepolia" }), /deploymentBlock/);
await assert.rejects(() => selectManifest({ root, network: "baseSepolia" }), /deploymentBlock/);
assert.deepEqual(await readFile(join(root, "deployments", "active.json")), beforeActive);
});
});
@@ -157,14 +178,30 @@ test("atomic writes remove the ignored staging file when rename fails", async ()
});
function manifest(overrides = {}) {
const baseSepolia = overrides.network === "base-sepolia";
const baseSepolia = overrides.network === "baseSepolia";
return {
schemaVersion: 1,
network: baseSepolia ? "base-sepolia" : "anvil",
network: baseSepolia ? "baseSepolia" : "anvil",
chainId: baseSepolia ? 84532 : 31337,
deploymentBlock: 1,
rpcUrl: baseSepolia ? "https://sepolia.base.org" : "http://127.0.0.1:8545",
explorerUrl: baseSepolia ? "https://sepolia.basescan.org" : "",
...(baseSepolia ? {} : { rpcUrl: "http://127.0.0.1:8545" }),
token: TOKEN,
proxy: PROXY,
implementation: IMPLEMENTATION,
owner: OWNER,
actors: baseSepolia ? [{ label: "owner", address: OWNER }] : localActors(),
...overrides,
};
}
function legacyManifest(overrides = {}) {
return {
schemaVersion: 1,
network: "anvil",
chainId: 31337,
deploymentBlock: 1,
rpcUrl: "http://127.0.0.1:8545",
explorerUrl: "",
token: TOKEN,
proxy: PROXY,
implementation: IMPLEMENTATION,
@@ -175,6 +212,14 @@ function manifest(overrides = {}) {
};
}
function localActors(aliceLabel = "Alice") {
return [
{ label: "owner", address: OWNER },
{ label: aliceLabel, address: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" },
{ label: "Bob", address: "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC" },
];
}
async function withFixture(fn) {
const root = await mkdtemp(join(tmpdir(), "uups-finalizer-"));
try {