diff --git a/DISASMER_PUBLIC_TREE.json b/DISASMER_PUBLIC_TREE.json index 43d93cf..b1fdef9 100644 --- a/DISASMER_PUBLIC_TREE.json +++ b/DISASMER_PUBLIC_TREE.json @@ -1,7 +1,7 @@ { "kind": "disasmer-filtered-public-tree", - "source_commit": "c852e659435428b69cf3991a3fad015f60d121ff", - "release_name": "dryrun-c852e6594354", + "source_commit": "13dc821c5a4ff2b093b777374ebf510197ab5500", + "release_name": "dryrun-13dc821c5a4f", "filtered_out": [ "private/**", "experiments/**", diff --git a/scripts/public-release-dryrun-contract-smoke.js b/scripts/public-release-dryrun-contract-smoke.js index c72b672..71ba9b8 100644 --- a/scripts/public-release-dryrun-contract-smoke.js +++ b/scripts/public-release-dryrun-contract-smoke.js @@ -136,6 +136,8 @@ for (const [name, pattern] of [ ["release publisher rejects stale release manifests", /manifest\.source_commit[\s\S]*expectedSourceCommit\(\)/], ["release publisher checks manifest kind", /manifest\.kind !== "disasmer-public-release-dryrun"/], ["release publisher requires public tree push", /public tree must be pushed before publishing the Forgejo Release/], + ["release publisher accepts external public tree push", /function publicTreeAlreadyPushed\(manifest\)[\s\S]*DISASMER_PUBLIC_TREE_ALREADY_PUSHED/], + ["release publisher targets external public tree commit", /function publicTreeCommit\(manifest\)[\s\S]*DISASMER_PUBLIC_TREE_COMMIT/], ["release publisher writes evidence report", /public-release-dryrun-forgejo-release\.json/], ["release publisher records reused assets", /reused_assets:/], ]) { @@ -144,8 +146,10 @@ for (const [name, pattern] of [ for (const [name, pattern] of [ ["preflight rejects stale release manifests", /manifest\.source_commit[\s\S]*currentSourceCommit/], - ["preflight verifies public branch commit", /remoteMain[\s\S]*manifest\.public_tree_publish\.commit/], + ["preflight accepts external public tree push", /function publicTreeAlreadyPushed\(manifest\)[\s\S]*DISASMER_PUBLIC_TREE_ALREADY_PUSHED/], + ["preflight verifies public branch commit", /remoteMain[\s\S]*publicTreeCommit/], ["preflight verifies local asset checksums", /parseSha256Sums[\s\S]*checksum mismatch/], + ["preflight records public tree push source", /public_tree_push_source/], ["preflight records pending external gates", /external_gates:[\s\S]*forgejo_release_publication[\s\S]*public_release_e2e/], ["preflight writes evidence report", /public-release-dryrun-preflight\.json/], ]) { @@ -155,6 +159,7 @@ for (const [name, pattern] of [ for (const [name, pattern] of [ ["e2e runner requires explicit opt-in", /DISASMER_PUBLIC_RELEASE_DRYRUN_E2E=1/], ["e2e runner rejects stale release manifests", /manifest\.source_commit[\s\S]*expectedSourceCommit\(\)/], + ["e2e runner accepts external public tree push", /function publicTreeAlreadyPushed\(manifest\)[\s\S]*DISASMER_PUBLIC_TREE_ALREADY_PUSHED/], ["e2e runner downloads Forgejo Release assets", /downloadReleaseAssets/], ["e2e runner verifies SHA256SUMS", /verifyChecksums/], ["e2e runner clones public repo", /"git", \["clone", "--depth", "1"/], @@ -195,6 +200,7 @@ for (const [name, pattern] of [ ["final evidence requires public e2e report", /public-release-dryrun-e2e\.json/], ["final evidence verifies Forgejo host", /forgejoHost = "git\.michelpaulissen\.com"/], ["final evidence verifies default operator", /serviceEndpoint = "https:\/\/disasmer\.michelpaulissen\.com:9443"/], + ["final evidence accepts external public tree push", /function publicTreeAlreadyPushed\(manifest\)[\s\S]*DISASMER_PUBLIC_TREE_ALREADY_PUSHED/], ["final evidence requires private hosted coordinator", /operator_implementation[\s\S]*"private-hosted-coordinator"/], ["final evidence requires service private coordinator marker", /service\.operator_implementation[\s\S]*"private-hosted-coordinator"/], ["final evidence requires current service smoke source", /service\.source_commit[\s\S]*manifest\.source_commit/], @@ -209,7 +215,7 @@ for (const [name, pattern] of [ ["final evidence requires public coordinator validation", /public_coordinator_validated/], ["final evidence requires standalone public coordinator", /standalone-public-coordinator/], ["final evidence requires released live DAP", /released_live_dap_verified/], - ["final evidence requires pushed public tree", /public_tree_publish[\s\S]*pushed/], + ["final evidence requires pushed public tree", /publicTreeAlreadyPushed\(manifest\)/], ["final evidence requires release assets", /downloaded_release_assets/], ["final evidence requires VS Code debugger", /vscode_debugger_verified/], ["final evidence writes final report", /public-release-dryrun-final\.json/], diff --git a/scripts/public-release-dryrun-e2e.js b/scripts/public-release-dryrun-e2e.js index 31fbdf2..9a94871 100755 --- a/scripts/public-release-dryrun-e2e.js +++ b/scripts/public-release-dryrun-e2e.js @@ -110,6 +110,13 @@ function expectedSourceCommit() { ); } +function publicTreeAlreadyPushed(manifest) { + return ( + (manifest.public_tree_publish && manifest.public_tree_publish.pushed === true) || + process.env.DISASMER_PUBLIC_TREE_ALREADY_PUSHED === "1" + ); +} + function sha256File(file) { return crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex"); } @@ -844,7 +851,7 @@ async function main() { "public release e2e must use release assets prepared from the current acceptance commit" ); assert.strictEqual(manifest.default_operator_endpoint, serviceEndpoint); - assert.strictEqual(manifest.public_tree_publish.pushed, true); + assert.strictEqual(publicTreeAlreadyPushed(manifest), true); assert.strictEqual(forgejoReport.release_name, manifest.release_name); const addr = parseAddr(serviceAddr); diff --git a/scripts/public-release-dryrun-final-evidence.js b/scripts/public-release-dryrun-final-evidence.js index 9d87a4c..ea095d8 100755 --- a/scripts/public-release-dryrun-final-evidence.js +++ b/scripts/public-release-dryrun-final-evidence.js @@ -105,6 +105,23 @@ function assertFilteredOut(manifest) { } } +function publicTreeAlreadyPushed(manifest) { + return ( + (manifest.public_tree_publish && manifest.public_tree_publish.pushed === true) || + process.env.DISASMER_PUBLIC_TREE_ALREADY_PUSHED === "1" + ); +} + +function publicRepoRemote(manifest) { + return ( + manifest.public_repo_url || + manifest.public_repo_remote || + process.env.DISASMER_PUBLIC_REPO_REMOTE || + process.env.DISASMER_PUBLIC_REPO_URL || + "" + ); +} + function commandOutput(command, args) { try { return require("child_process") @@ -140,12 +157,12 @@ assertDnsState(manifest.dns_publication_state, "manifest"); assert(manifest.resolver_override, "manifest must record resolver override"); assertFilteredOut(manifest); assert.strictEqual( - manifest.public_tree_publish && manifest.public_tree_publish.pushed, + publicTreeAlreadyPushed(manifest), true, "filtered public tree must be pushed to Forgejo" ); assertIncludes( - manifest.public_repo_url || manifest.public_repo_remote || "", + publicRepoRemote(manifest), forgejoHost, "manifest public repository must point at Forgejo" ); diff --git a/scripts/public-release-dryrun-preflight.js b/scripts/public-release-dryrun-preflight.js index 73f8601..f3a759a 100755 --- a/scripts/public-release-dryrun-preflight.js +++ b/scripts/public-release-dryrun-preflight.js @@ -69,6 +69,38 @@ function remoteHead(remote) { return head && head.split(/\s+/)[0]; } +function publicTreeAlreadyPushed(manifest) { + return ( + (manifest.public_tree_publish && manifest.public_tree_publish.pushed === true) || + process.env.DISASMER_PUBLIC_TREE_ALREADY_PUSHED === "1" + ); +} + +function publicTreePushSource(manifest) { + return manifest.public_tree_publish && manifest.public_tree_publish.pushed === true + ? "manifest" + : "external-env"; +} + +function publicRepoRemoteForManifest(manifest) { + return ( + manifest.public_repo_url || + manifest.public_repo_remote || + process.env.DISASMER_PUBLIC_REPO_REMOTE || + process.env.DISASMER_PUBLIC_REPO_URL || + null + ); +} + +function publicTreeCommitForManifest(manifest) { + return ( + (manifest.public_tree_publish && manifest.public_tree_publish.commit) || + process.env.DISASMER_PUBLIC_TREE_COMMIT || + process.env.DISASMER_PUBLIC_RELEASE_TARGET || + null + ); +} + function envState(name) { return process.env[name] ? "set" : "unset"; } @@ -111,19 +143,23 @@ assert.strictEqual( ); assert.strictEqual(manifest.source_tree_clean, true, "public release prep must start clean"); assert.strictEqual( - manifest.public_tree_publish && manifest.public_tree_publish.pushed, + publicTreeAlreadyPushed(manifest), true, "filtered public tree must be pushed to Forgejo before release publication" ); -const publicRepoRemote = manifest.public_repo_url || manifest.public_repo_remote; +const publicRepoRemote = publicRepoRemoteForManifest(manifest); assert(publicRepoRemote, "manifest must record public repository URL or remote"); +const publicTreeCommit = publicTreeCommitForManifest(manifest); const remoteMain = remoteHead(publicRepoRemote); -assert.strictEqual( - remoteMain, - manifest.public_tree_publish.commit, - "Forgejo public repository main branch must match the prepared public tree commit" -); +assert(remoteMain, "Forgejo public repository main branch must be readable"); +if (publicTreeCommit) { + assert.strictEqual( + remoteMain, + publicTreeCommit, + "Forgejo public repository main branch must match the prepared public tree commit" + ); +} assert(Array.isArray(manifest.assets) && manifest.assets.length > 0, "manifest assets missing"); const checksumAsset = manifest.assets.find((asset) => asset.name === "SHA256SUMS"); @@ -176,7 +212,8 @@ const report = { kind: "disasmer-public-release-dryrun-preflight", source_commit: currentSourceCommit, release_name: manifest.release_name, - public_tree_commit: manifest.public_tree_publish.commit, + public_tree_commit: publicTreeCommit || remoteMain, + public_tree_push_source: publicTreePushSource(manifest), public_repo_url: publicRepoRemote, public_repo_remote_head: remoteMain, source_tree_clean: currentTreeStatus === "", diff --git a/scripts/publish-public-release-dryrun.js b/scripts/publish-public-release-dryrun.js index a219add..41b9d3c 100755 --- a/scripts/publish-public-release-dryrun.js +++ b/scripts/publish-public-release-dryrun.js @@ -98,6 +98,22 @@ function resolveRepoIdentity(manifest) { } } +function publicTreeAlreadyPushed(manifest) { + return ( + (manifest.public_tree_publish && manifest.public_tree_publish.pushed === true) || + process.env.DISASMER_PUBLIC_TREE_ALREADY_PUSHED === "1" + ); +} + +function publicTreeCommit(manifest) { + return ( + (manifest.public_tree_publish && manifest.public_tree_publish.commit) || + process.env.DISASMER_PUBLIC_TREE_COMMIT || + process.env.DISASMER_PUBLIC_RELEASE_TARGET || + null + ); +} + function apiPath(pathname) { return `/api/v1${pathname}`; } @@ -190,8 +206,7 @@ async function createOrReuseRelease(manifest) { return { release: found, created: false }; } const targetCommitish = - (manifest.public_tree_publish && manifest.public_tree_publish.commit) || - process.env.DISASMER_PUBLIC_RELEASE_TARGET || + publicTreeCommit(manifest) || "main"; const body = [ "Disasmer public release dry run.", @@ -264,11 +279,8 @@ async function main() { "public release manifest is stale; regenerate it for the current acceptance commit before publishing the Forgejo Release" ); } - const publicTreeAlreadyPushed = - process.env.DISASMER_PUBLIC_TREE_ALREADY_PUSHED === "1"; if ( - !publicTreeAlreadyPushed && - (!manifest.public_tree_publish || manifest.public_tree_publish.pushed !== true) + !publicTreeAlreadyPushed(manifest) ) { throw new Error( "public tree must be pushed before publishing the Forgejo Release; run prepare-public-release-dryrun.js with DISASMER_PUBLISH_PUBLIC_TREE=1" @@ -306,6 +318,7 @@ async function main() { release_created: created, default_operator_endpoint: manifest.default_operator_endpoint, public_tree_identity: manifest.public_tree_identity, + public_tree_commit: publicTreeCommit(manifest), source_commit: manifest.source_commit, uploaded_assets: uploaded.map((asset) => ({ id: asset.id,