mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(release): require requested validation children (#118031)
* fix(release): require requested validation children * fix(release): bind Telegram reuse evidence
This commit is contained in:
@@ -308,6 +308,10 @@ jobs:
|
||||
RELEASE_PACKAGE_SPEC: ${{ inputs.release_package_spec }}
|
||||
PACKAGE_ACCEPTANCE_PACKAGE_SPEC: ${{ inputs.package_acceptance_package_spec }}
|
||||
CODEX_PLUGIN_SPEC: ${{ inputs.codex_plugin_spec }}
|
||||
NPM_TELEGRAM_PACKAGE_SPEC: ${{ inputs.npm_telegram_package_spec }}
|
||||
NPM_TELEGRAM_PROVIDER_MODE: ${{ inputs.npm_telegram_provider_mode }}
|
||||
NPM_TELEGRAM_SCENARIO: ${{ inputs.npm_telegram_scenario }}
|
||||
ALLOW_UNRELEASED_CHANGELOG: ${{ inputs.allow_unreleased_changelog || (inputs.target_context_ref == '' && (inputs.ref == 'main' || inputs.ref == 'refs/heads/main')) }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Lane-selection inputs must match the prior run's manifest exactly;
|
||||
@@ -321,6 +325,10 @@ jobs:
|
||||
--arg releasePackageSpec "$RELEASE_PACKAGE_SPEC" \
|
||||
--arg packageAcceptancePackageSpec "$PACKAGE_ACCEPTANCE_PACKAGE_SPEC" \
|
||||
--arg codexPluginSpec "$CODEX_PLUGIN_SPEC" \
|
||||
--arg npmTelegramPackageSpec "$NPM_TELEGRAM_PACKAGE_SPEC" \
|
||||
--arg npmTelegramProviderMode "$NPM_TELEGRAM_PROVIDER_MODE" \
|
||||
--arg npmTelegramScenario "$NPM_TELEGRAM_SCENARIO" \
|
||||
--arg allowUnreleasedChangelog "$ALLOW_UNRELEASED_CHANGELOG" \
|
||||
'{
|
||||
provider: $provider,
|
||||
mode: $mode,
|
||||
@@ -329,7 +337,11 @@ jobs:
|
||||
crossOsSuiteFilter: $crossOsSuiteFilter,
|
||||
releasePackageSpec: $releasePackageSpec,
|
||||
packageAcceptancePackageSpec: $packageAcceptancePackageSpec,
|
||||
codexPluginSpec: $codexPluginSpec
|
||||
codexPluginSpec: $codexPluginSpec,
|
||||
npmTelegramPackageSpec: $npmTelegramPackageSpec,
|
||||
npmTelegramProviderMode: $npmTelegramProviderMode,
|
||||
npmTelegramScenario: $npmTelegramScenario,
|
||||
allowUnreleasedChangelog: $allowUnreleasedChangelog
|
||||
}')"
|
||||
bash workflow/scripts/github/find-reusable-release-validation.sh \
|
||||
--target-sha "$TARGET_SHA" \
|
||||
@@ -937,8 +949,8 @@ jobs:
|
||||
|
||||
npm_telegram:
|
||||
name: Run package Telegram E2E
|
||||
needs: [resolve_target]
|
||||
if: ${{ always() && needs.resolve_target.result == 'success' && inputs.rerun_group == 'npm-telegram' && (inputs.npm_telegram_package_spec != '' || inputs.release_package_spec != '') }}
|
||||
needs: [resolve_target, evidence_reuse]
|
||||
if: ${{ always() && needs.resolve_target.result == 'success' && contains(fromJSON('["all","npm-telegram"]'), inputs.rerun_group) && (inputs.npm_telegram_package_spec != '' || inputs.release_package_spec != '') && needs.evidence_reuse.outputs.reuse != 'true' }}
|
||||
continue-on-error: ${{ startsWith(github.ref, 'refs/heads/tideclaw/alpha/') }}
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: ${{ inputs.release_profile == 'full' && 360 || 60 }}
|
||||
@@ -1019,6 +1031,7 @@ jobs:
|
||||
DOCKER_RUNTIME_ASSETS_PREFLIGHT_RESULT: ${{ needs.docker_runtime_assets_preflight.result }}
|
||||
PREPARE_RELEASE_CANDIDATE_RESULT: ${{ needs.prepare_release_candidate.result }}
|
||||
RELEASE_PACKAGE_SPEC: ${{ inputs.release_package_spec }}
|
||||
NPM_TELEGRAM_PACKAGE_SPEC: ${{ inputs.npm_telegram_package_spec }}
|
||||
PACKAGE_ACCEPTANCE_PACKAGE_SPEC: ${{ inputs.package_acceptance_package_spec }}
|
||||
EVIDENCE_REUSE: ${{ needs.evidence_reuse.outputs.reuse }}
|
||||
EVIDENCE_ROOT_RUN_ID: ${{ needs.evidence_reuse.outputs.evidence_root_run_id }}
|
||||
@@ -1289,8 +1302,12 @@ jobs:
|
||||
normal_ci_required=0
|
||||
plugin_prerelease_required=0
|
||||
release_checks_required=0
|
||||
npm_telegram_required=0
|
||||
performance_required=0
|
||||
candidate_required=0
|
||||
if [[ "$RERUN_GROUP" == "npm-telegram" || ( "$RERUN_GROUP" == "all" && ( -n "${NPM_TELEGRAM_PACKAGE_SPEC// }" || -n "${RELEASE_PACKAGE_SPEC// }" ) ) ]]; then
|
||||
npm_telegram_required=1
|
||||
fi
|
||||
if [[ -z "${RELEASE_PACKAGE_SPEC// }" && -z "${PACKAGE_ACCEPTANCE_PACKAGE_SPEC// }" ]]; then
|
||||
case "$RERUN_GROUP" in
|
||||
all|plugin-prerelease|release-checks|cross-os|live-e2e|package)
|
||||
@@ -1306,6 +1323,11 @@ jobs:
|
||||
# Lanes were skipped because a prior green validation covers this
|
||||
# target; re-verify the chain-root run and its recorded child runs
|
||||
# so evidence that went stale after resolution cannot pass.
|
||||
reused_npm_telegram_run_id="$(jq -r '.childRuns.npmTelegram // ""' <<< "$EVIDENCE_MANIFEST")"
|
||||
if [[ "$npm_telegram_required" == "1" && -z "${reused_npm_telegram_run_id// }" ]]; then
|
||||
echo "::error::Reused evidence did not record the required npm Telegram child run."
|
||||
failed=1
|
||||
fi
|
||||
evidence_state="$(gh_with_retry run view "$EVIDENCE_ROOT_RUN_ID" --json status,conclusion --jq '(.status // "") + "/" + (.conclusion // "")')"
|
||||
if [[ "$evidence_state" != "completed/success" ]]; then
|
||||
echo "::error::Reused evidence run ${EVIDENCE_ROOT_RUN_ID} is ${evidence_state}; evidence is no longer valid."
|
||||
@@ -1389,7 +1411,7 @@ jobs:
|
||||
fi
|
||||
|
||||
if [[ "$NPM_TELEGRAM_RESULT" == "skipped" && -z "${NPM_TELEGRAM_RUN_ID// }" ]]; then
|
||||
check_child "npm_telegram" "" 0 || failed=1
|
||||
check_child "npm_telegram" "" "$npm_telegram_required" || failed=1
|
||||
elif [[ "$CHILD_WORKFLOW_REF" =~ ^tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then
|
||||
check_child "npm_telegram" "$NPM_TELEGRAM_RUN_ID" 0 || echo "::warning::npm_telegram is advisory for Tideclaw alpha validation."
|
||||
else
|
||||
@@ -1534,6 +1556,10 @@ jobs:
|
||||
RELEASE_PACKAGE_SPEC: ${{ inputs.release_package_spec }}
|
||||
PACKAGE_ACCEPTANCE_PACKAGE_SPEC: ${{ inputs.package_acceptance_package_spec }}
|
||||
CODEX_PLUGIN_SPEC: ${{ inputs.codex_plugin_spec }}
|
||||
NPM_TELEGRAM_PACKAGE_SPEC: ${{ inputs.npm_telegram_package_spec }}
|
||||
NPM_TELEGRAM_PROVIDER_MODE: ${{ inputs.npm_telegram_provider_mode }}
|
||||
NPM_TELEGRAM_SCENARIO: ${{ inputs.npm_telegram_scenario }}
|
||||
ALLOW_UNRELEASED_CHANGELOG: ${{ inputs.allow_unreleased_changelog || (inputs.target_context_ref == '' && (inputs.ref == 'main' || inputs.ref == 'refs/heads/main')) }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
manifest_dir="${RUNNER_TEMP}/full-release-validation"
|
||||
@@ -1605,6 +1631,10 @@ jobs:
|
||||
--arg releasePackageSpec "$RELEASE_PACKAGE_SPEC" \
|
||||
--arg packageAcceptancePackageSpec "$PACKAGE_ACCEPTANCE_PACKAGE_SPEC" \
|
||||
--arg codexPluginSpec "$CODEX_PLUGIN_SPEC" \
|
||||
--arg npmTelegramPackageSpec "$NPM_TELEGRAM_PACKAGE_SPEC" \
|
||||
--arg npmTelegramProviderMode "$NPM_TELEGRAM_PROVIDER_MODE" \
|
||||
--arg npmTelegramScenario "$NPM_TELEGRAM_SCENARIO" \
|
||||
--arg allowUnreleasedChangelog "$ALLOW_UNRELEASED_CHANGELOG" \
|
||||
'{
|
||||
version: 3,
|
||||
workflowName: $workflowName,
|
||||
@@ -1627,7 +1657,11 @@ jobs:
|
||||
crossOsSuiteFilter: $crossOsSuiteFilter,
|
||||
releasePackageSpec: $releasePackageSpec,
|
||||
packageAcceptancePackageSpec: $packageAcceptancePackageSpec,
|
||||
codexPluginSpec: $codexPluginSpec
|
||||
codexPluginSpec: $codexPluginSpec,
|
||||
npmTelegramPackageSpec: $npmTelegramPackageSpec,
|
||||
npmTelegramProviderMode: $npmTelegramProviderMode,
|
||||
npmTelegramScenario: $npmTelegramScenario,
|
||||
allowUnreleasedChangelog: $allowUnreleasedChangelog
|
||||
},
|
||||
controls: {
|
||||
stableSoakRequired: ($releaseProfile == "stable" or $releaseProfile == "full"),
|
||||
|
||||
@@ -587,6 +587,13 @@ jobs:
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Require requested Buzz QA runner
|
||||
if: always() && steps.resolve_buzz.outcome == 'success' && steps.resolve_buzz.outputs.available != 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::error::The selected ref does not declare the requested Buzz QA runner."
|
||||
exit 1
|
||||
|
||||
run_live_telegram:
|
||||
name: Run Telegram live QA lane with Convex leases
|
||||
needs: [authorize_actor, validate_selected_ref]
|
||||
|
||||
@@ -253,7 +253,17 @@ for ((index = 0; index < run_count; index += 1)); do
|
||||
and (.verifier.schemaVersion == 3)
|
||||
and (.verifier.sourceSha == $verifier_sha)
|
||||
and ([.children[].role] | sort) ==
|
||||
["normalCi", "pluginPrerelease", "productPerformance", "releaseChecks"]
|
||||
(if (
|
||||
.rerunGroup == "all"
|
||||
and (
|
||||
((.validationInputs.npmTelegramPackageSpec // "") | length) > 0
|
||||
or ((.validationInputs.releasePackageSpec // "") | length) > 0
|
||||
)
|
||||
) then
|
||||
["normalCi", "npmTelegram", "pluginPrerelease", "productPerformance", "releaseChecks"]
|
||||
else
|
||||
["normalCi", "pluginPrerelease", "productPerformance", "releaseChecks"]
|
||||
end)
|
||||
and ([.children[].runId] | length == (unique | length))
|
||||
and ([.children[]
|
||||
| select(.role == "productPerformance")
|
||||
|
||||
@@ -39,7 +39,10 @@ export function expectedChildDispatches(
|
||||
trustedRef: string;
|
||||
workflow: string;
|
||||
}[];
|
||||
export function requiredChildKeysForRerunGroup(rerunGroup: unknown): Set<string>;
|
||||
export function requiredChildKeysForRerunGroup(
|
||||
rerunGroup: unknown,
|
||||
validationInputs?: Record<string, unknown>,
|
||||
): Set<string>;
|
||||
export function expectedSelectedChildDispatches(
|
||||
parentRunId: unknown,
|
||||
parentRunAttempt: unknown,
|
||||
|
||||
@@ -180,12 +180,22 @@ export function expectedChildDispatches(parentRunId, parentRunAttempt, parentWor
|
||||
}));
|
||||
}
|
||||
|
||||
export function requiredChildKeysForRerunGroup(rerunGroup) {
|
||||
export function requiredChildKeysForRerunGroup(rerunGroup, validationInputs = {}) {
|
||||
const childKeys = RERUN_GROUP_CHILD_KEYS.get(rerunGroup);
|
||||
if (!childKeys) {
|
||||
throw new Error(`release validation manifest rerun group is invalid: ${rerunGroup}`);
|
||||
}
|
||||
return new Set(childKeys);
|
||||
const selectedKeys = new Set(childKeys);
|
||||
if (
|
||||
rerunGroup === "all" &&
|
||||
((typeof validationInputs.npmTelegramPackageSpec === "string" &&
|
||||
validationInputs.npmTelegramPackageSpec.length > 0) ||
|
||||
(typeof validationInputs.releasePackageSpec === "string" &&
|
||||
validationInputs.releasePackageSpec.length > 0))
|
||||
) {
|
||||
selectedKeys.add("npmTelegram");
|
||||
}
|
||||
return selectedKeys;
|
||||
}
|
||||
|
||||
export function expectedSelectedChildDispatches(
|
||||
@@ -1356,7 +1366,10 @@ export function validateReleaseRunEvidence(
|
||||
);
|
||||
}
|
||||
}
|
||||
const selectedKeys = requiredChildKeysForRerunGroup(rootEvidence.manifest.rerunGroup);
|
||||
const selectedKeys = requiredChildKeysForRerunGroup(
|
||||
rootEvidence.manifest.rerunGroup,
|
||||
rootEvidence.manifest.validationInputs,
|
||||
);
|
||||
const expectedChildren = expectedSelectedChildDispatches(
|
||||
rootEvidence.manifest.runId,
|
||||
rootEvidence.manifest.runAttempt,
|
||||
@@ -1750,51 +1763,53 @@ async function main() {
|
||||
);
|
||||
}
|
||||
|
||||
const selectedKeys = requiredChildKeysForRerunGroup(
|
||||
sourceManifest.rerunGroup,
|
||||
sourceManifest.validationInputs,
|
||||
);
|
||||
const expectedChildren = expectedSelectedChildDispatches(
|
||||
sourceManifest.runId,
|
||||
sourceManifest.runAttempt,
|
||||
sourceManifest.workflowRef,
|
||||
requiredChildKeysForRerunGroup(sourceManifest.rerunGroup),
|
||||
selectedKeys,
|
||||
);
|
||||
const sourceParentJobs = findParentJobsAll(sourceManifest.runId, repository);
|
||||
children = manifestChildEntries(
|
||||
sourceManifest,
|
||||
expectedChildren,
|
||||
requiredChildKeysForRerunGroup(sourceManifest.rerunGroup),
|
||||
).map(({ child, runId: childRunId }) => {
|
||||
const run = githubRestJson(`actions/runs/${childRunId}`, repository);
|
||||
const originAttempt = resolveManifestChildOriginAttempt(
|
||||
run,
|
||||
child,
|
||||
sourceManifest,
|
||||
sourceParentJobs,
|
||||
);
|
||||
if (originAttempt === undefined) {
|
||||
throw new Error(`manifest child dispatch tuple mismatch: ${child.name}`);
|
||||
}
|
||||
const parentJob = selectManifestParentJob(
|
||||
sourceParentJobs,
|
||||
child,
|
||||
sourceManifest,
|
||||
originAttempt,
|
||||
);
|
||||
const validatedRun = validateManifestChildRun(
|
||||
run,
|
||||
child,
|
||||
childRunId,
|
||||
{ ...sourceManifest, workflowSha: sourceParent.headSha },
|
||||
sourceParentJobs,
|
||||
parentJobLog(parentJob.id, repository),
|
||||
repository,
|
||||
);
|
||||
if (child.manifestKey === "productPerformance") {
|
||||
validatePerformanceArtifactOnlyJobs(
|
||||
findParentJobsAll(childRunId, repository),
|
||||
run.run_attempt,
|
||||
children = manifestChildEntries(sourceManifest, expectedChildren, selectedKeys).map(
|
||||
({ child, runId: childRunId }) => {
|
||||
const run = githubRestJson(`actions/runs/${childRunId}`, repository);
|
||||
const originAttempt = resolveManifestChildOriginAttempt(
|
||||
run,
|
||||
child,
|
||||
sourceManifest,
|
||||
sourceParentJobs,
|
||||
);
|
||||
}
|
||||
return { child, run: validatedRun };
|
||||
});
|
||||
if (originAttempt === undefined) {
|
||||
throw new Error(`manifest child dispatch tuple mismatch: ${child.name}`);
|
||||
}
|
||||
const parentJob = selectManifestParentJob(
|
||||
sourceParentJobs,
|
||||
child,
|
||||
sourceManifest,
|
||||
originAttempt,
|
||||
);
|
||||
const validatedRun = validateManifestChildRun(
|
||||
run,
|
||||
child,
|
||||
childRunId,
|
||||
{ ...sourceManifest, workflowSha: sourceParent.headSha },
|
||||
sourceParentJobs,
|
||||
parentJobLog(parentJob.id, repository),
|
||||
repository,
|
||||
);
|
||||
if (child.manifestKey === "productPerformance") {
|
||||
validatePerformanceArtifactOnlyJobs(
|
||||
findParentJobsAll(childRunId, repository),
|
||||
run.run_attempt,
|
||||
);
|
||||
}
|
||||
return { child, run: validatedRun };
|
||||
},
|
||||
);
|
||||
} else {
|
||||
console.log("candidate-sha: unavailable (release validation manifest not uploaded)");
|
||||
if (parent.status === "completed" && parent.conclusion === "success") {
|
||||
|
||||
@@ -26,6 +26,10 @@ const DEFAULT_INPUTS = {
|
||||
releasePackageSpec: "",
|
||||
packageAcceptancePackageSpec: "",
|
||||
codexPluginSpec: "",
|
||||
npmTelegramPackageSpec: "",
|
||||
npmTelegramProviderMode: "mock-openai",
|
||||
npmTelegramScenario: "",
|
||||
allowUnreleasedChangelog: "false",
|
||||
};
|
||||
|
||||
interface ParentTuple {
|
||||
@@ -210,6 +214,10 @@ function normalizedEvidence(options: {
|
||||
const shaPinned = workflowRef.startsWith("release-ci/");
|
||||
const validationInputs =
|
||||
options.validationInputs === undefined ? DEFAULT_INPUTS : options.validationInputs;
|
||||
const npmTelegramRequired =
|
||||
validationInputs !== null &&
|
||||
(validationInputs.npmTelegramPackageSpec.length > 0 ||
|
||||
validationInputs.releasePackageSpec.length > 0);
|
||||
const manifest = {
|
||||
version: shaPinned ? 3 : 2,
|
||||
workflowName: "Full Release Validation",
|
||||
@@ -232,7 +240,7 @@ function normalizedEvidence(options: {
|
||||
},
|
||||
childRuns: {
|
||||
normalCi: "201",
|
||||
npmTelegram: "",
|
||||
npmTelegram: npmTelegramRequired ? "205" : "",
|
||||
pluginPrerelease: "202",
|
||||
releaseChecks: "203",
|
||||
productPerformance: {
|
||||
@@ -292,6 +300,19 @@ function normalizedEvidence(options: {
|
||||
"openclaw-release-checks.yml",
|
||||
"-release-checks",
|
||||
],
|
||||
...(npmTelegramRequired
|
||||
? ([
|
||||
[
|
||||
"npmTelegram",
|
||||
"205",
|
||||
1,
|
||||
2,
|
||||
"NPM Telegram Beta E2E",
|
||||
"npm-telegram-beta-e2e.yml",
|
||||
"-npm-telegram",
|
||||
],
|
||||
] as const)
|
||||
: []),
|
||||
["productPerformance", "204", 3, 2, "OpenClaw Performance", "openclaw-performance.yml", ""],
|
||||
] as const;
|
||||
const children = roles.map(
|
||||
@@ -551,6 +572,33 @@ describe("scripts/github/find-reusable-release-validation.sh", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("reuses npm Telegram evidence only when its selectors match exactly", () => {
|
||||
const { clone, priorSha } = getSharedRepo();
|
||||
const validationInputs = {
|
||||
...DEFAULT_INPUTS,
|
||||
npmTelegramPackageSpec: "openclaw@2026.7.2-beta.7",
|
||||
npmTelegramProviderMode: "live-frontier",
|
||||
npmTelegramScenario: "telegram-status-command",
|
||||
};
|
||||
const record = normalizedEvidence({ targetSha: priorSha, validationInputs });
|
||||
const { binDir, fixtures, validatorPath } = setUpFixtures([{ record, runId: "111" }]);
|
||||
|
||||
const result = runResolver({
|
||||
binDir,
|
||||
fixtures,
|
||||
inputs: validationInputs,
|
||||
repoDir: clone,
|
||||
targetSha: priorSha,
|
||||
validatorPath,
|
||||
});
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(parseOutput(result.stdout)).toMatchObject({
|
||||
evidence_run_id: "111",
|
||||
reuse: "true",
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects noncanonical release refs and workflow SHAs outside trusted main", () => {
|
||||
const { clone, priorSha } = getSharedRepo();
|
||||
const record = normalizedEvidence({ targetSha: priorSha });
|
||||
@@ -827,6 +875,38 @@ describe("scripts/github/find-reusable-release-validation.sh", () => {
|
||||
recordOptions: { validationInputs: { ...DEFAULT_INPUTS, provider: "anthropic" } },
|
||||
resolverOptions: {},
|
||||
},
|
||||
{
|
||||
expected: "validation inputs differ",
|
||||
label: "different npm Telegram package",
|
||||
recordOptions: {
|
||||
validationInputs: { ...DEFAULT_INPUTS, npmTelegramPackageSpec: "openclaw@old" },
|
||||
},
|
||||
resolverOptions: {},
|
||||
},
|
||||
{
|
||||
expected: "validation inputs differ",
|
||||
label: "different npm Telegram provider mode",
|
||||
recordOptions: {
|
||||
validationInputs: { ...DEFAULT_INPUTS, npmTelegramProviderMode: "live-frontier" },
|
||||
},
|
||||
resolverOptions: {},
|
||||
},
|
||||
{
|
||||
expected: "validation inputs differ",
|
||||
label: "different npm Telegram scenario",
|
||||
recordOptions: {
|
||||
validationInputs: { ...DEFAULT_INPUTS, npmTelegramScenario: "telegram-status-command" },
|
||||
},
|
||||
resolverOptions: {},
|
||||
},
|
||||
{
|
||||
expected: "validation inputs differ",
|
||||
label: "different unreleased changelog policy",
|
||||
recordOptions: {
|
||||
validationInputs: { ...DEFAULT_INPUTS, allowUnreleasedChangelog: "true" },
|
||||
},
|
||||
resolverOptions: {},
|
||||
},
|
||||
{
|
||||
expected: "soak false differs from true",
|
||||
label: "missing required soak",
|
||||
|
||||
@@ -3173,6 +3173,14 @@ describe("package artifact reuse", () => {
|
||||
expect(workflowStep(buzzJob, "Upload Buzz QA artifacts").with?.path).toBe(
|
||||
"${{ steps.resolve_buzz.outputs.output_dir }}",
|
||||
);
|
||||
const requireBuzz = workflowStep(buzzJob, "Require requested Buzz QA runner");
|
||||
expect(requireBuzz.if).toBe(
|
||||
"always() && steps.resolve_buzz.outcome == 'success' && steps.resolve_buzz.outputs.available != 'true'",
|
||||
);
|
||||
expect(requireBuzz.run).toContain(
|
||||
"The selected ref does not declare the requested Buzz QA runner.",
|
||||
);
|
||||
expect(requireBuzz.run).toContain("exit 1");
|
||||
});
|
||||
|
||||
it("runs live transport lanes nightly while release checks stay gated", () => {
|
||||
@@ -3576,22 +3584,41 @@ describe("package artifact reuse", () => {
|
||||
|
||||
it("runs full release children from the trusted workflow ref", () => {
|
||||
const workflow = readFileSync(FULL_RELEASE_VALIDATION_WORKFLOW, "utf8");
|
||||
const evidenceReuseJob = workflowJob(FULL_RELEASE_VALIDATION_WORKFLOW, "evidence_reuse");
|
||||
const npmTelegramJob = workflowJob(FULL_RELEASE_VALIDATION_WORKFLOW, "npm_telegram");
|
||||
const performanceJob = workflowJob(FULL_RELEASE_VALIDATION_WORKFLOW, "performance");
|
||||
const summaryJob = workflowJob(FULL_RELEASE_VALIDATION_WORKFLOW, "summary");
|
||||
const evidenceReuseStep = workflowStep(evidenceReuseJob, "Find reusable validation evidence");
|
||||
const dispatchStep = workflowStep(npmTelegramJob, "Dispatch and monitor npm Telegram E2E");
|
||||
const manifestStep = workflowStep(summaryJob, "Write release validation manifest");
|
||||
|
||||
expect(workflow).toContain("CHILD_WORKFLOW_REF: ${{ github.ref_name }}");
|
||||
expect(workflow).toContain('gh workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1');
|
||||
expect(npmTelegramJob.name).toBe("Run package Telegram E2E");
|
||||
expect(npmTelegramJob.needs).toEqual(["resolve_target"]);
|
||||
expect(npmTelegramJob.needs).toEqual(["resolve_target", "evidence_reuse"]);
|
||||
expect(npmTelegramJob["timeout-minutes"]).toBe(
|
||||
"${{ inputs.release_profile == 'full' && 360 || 60 }}",
|
||||
);
|
||||
expect(performanceJob["timeout-minutes"]).toBe(
|
||||
"${{ inputs.release_profile == 'full' && 360 || 120 }}",
|
||||
);
|
||||
expect(npmTelegramJob.if).toContain("inputs.rerun_group == 'npm-telegram'");
|
||||
expect(npmTelegramJob.if).not.toContain("inputs.rerun_group == 'all'");
|
||||
expect(npmTelegramJob.if).toContain(
|
||||
'contains(fromJSON(\'["all","npm-telegram"]\'), inputs.rerun_group)',
|
||||
);
|
||||
expect(npmTelegramJob.if).toContain("needs.evidence_reuse.outputs.reuse != 'true'");
|
||||
expect(evidenceReuseStep.env).toMatchObject({
|
||||
ALLOW_UNRELEASED_CHANGELOG:
|
||||
"${{ inputs.allow_unreleased_changelog || (inputs.target_context_ref == '' && (inputs.ref == 'main' || inputs.ref == 'refs/heads/main')) }}",
|
||||
NPM_TELEGRAM_PACKAGE_SPEC: "${{ inputs.npm_telegram_package_spec }}",
|
||||
NPM_TELEGRAM_PROVIDER_MODE: "${{ inputs.npm_telegram_provider_mode }}",
|
||||
NPM_TELEGRAM_SCENARIO: "${{ inputs.npm_telegram_scenario }}",
|
||||
});
|
||||
expectTextToIncludeAll(evidenceReuseStep.run, [
|
||||
"npmTelegramPackageSpec: $npmTelegramPackageSpec",
|
||||
"npmTelegramProviderMode: $npmTelegramProviderMode",
|
||||
"npmTelegramScenario: $npmTelegramScenario",
|
||||
"allowUnreleasedChangelog: $allowUnreleasedChangelog",
|
||||
]);
|
||||
expect(dispatchStep.env).toEqual({
|
||||
CHILD_WORKFLOW_KIND: "npm-telegram",
|
||||
CHILD_WORKFLOW_REF: "${{ github.ref_name }}",
|
||||
@@ -3603,6 +3630,19 @@ describe("package artifact reuse", () => {
|
||||
SCENARIO: "${{ inputs.npm_telegram_scenario }}",
|
||||
TARGET_SHA: "${{ needs.resolve_target.outputs.sha }}",
|
||||
});
|
||||
expect(manifestStep.env).toMatchObject({
|
||||
ALLOW_UNRELEASED_CHANGELOG:
|
||||
"${{ inputs.allow_unreleased_changelog || (inputs.target_context_ref == '' && (inputs.ref == 'main' || inputs.ref == 'refs/heads/main')) }}",
|
||||
NPM_TELEGRAM_PACKAGE_SPEC: "${{ inputs.npm_telegram_package_spec }}",
|
||||
NPM_TELEGRAM_PROVIDER_MODE: "${{ inputs.npm_telegram_provider_mode }}",
|
||||
NPM_TELEGRAM_SCENARIO: "${{ inputs.npm_telegram_scenario }}",
|
||||
});
|
||||
expectTextToIncludeAll(manifestStep.run, [
|
||||
"npmTelegramPackageSpec: $npmTelegramPackageSpec",
|
||||
"npmTelegramProviderMode: $npmTelegramProviderMode",
|
||||
"npmTelegramScenario: $npmTelegramScenario",
|
||||
"allowUnreleasedChangelog: $allowUnreleasedChangelog",
|
||||
]);
|
||||
expectTextToIncludeAll(dispatchStep.run, [
|
||||
'dispatch_id="full-release-validation-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}-npm-telegram"',
|
||||
'dispatch_output="$(gh workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1)"',
|
||||
@@ -3626,6 +3666,10 @@ describe("package artifact reuse", () => {
|
||||
"Verify release checks accepted Tideclaw alpha advisory lanes",
|
||||
"release_checks_advisory_only",
|
||||
"release_check_blocking_job",
|
||||
'if [[ "$RERUN_GROUP" == "npm-telegram" || ( "$RERUN_GROUP" == "all"',
|
||||
"npm_telegram_required=1",
|
||||
"Reused evidence did not record the required npm Telegram child run.",
|
||||
'check_child "npm_telegram" "" "$npm_telegram_required"',
|
||||
'if [[ "$RELEASE_PROFILE" == "beta" && "$1" == "Run package acceptance / Telegram package acceptance / "* ]]; then',
|
||||
'or (.name | startswith("Run QA Lab runtime-pair lane ("))',
|
||||
'or .name == "Run QA Lab live Discord lane"',
|
||||
|
||||
@@ -236,10 +236,14 @@ function rawManifest({
|
||||
runReleaseSoak: "false",
|
||||
targetSha,
|
||||
validationInputs: {
|
||||
allowUnreleasedChangelog: "false",
|
||||
codexPluginSpec: "",
|
||||
crossOsSuiteFilter: "",
|
||||
liveSuiteFilter: "",
|
||||
mode: "direct",
|
||||
npmTelegramPackageSpec: "",
|
||||
npmTelegramProviderMode: "mock-openai",
|
||||
npmTelegramScenario: "",
|
||||
packageAcceptancePackageSpec: "",
|
||||
provider: "openai",
|
||||
releasePackageSpec: "",
|
||||
@@ -1308,6 +1312,35 @@ describe("release CI summary child correlation", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("requires the npm Telegram child for all-validation with an effective package spec", () => {
|
||||
const raw = rawManifest({});
|
||||
raw.childRuns.npmTelegram = "505";
|
||||
raw.validationInputs.npmTelegramPackageSpec = "openclaw@beta";
|
||||
const manifest = validateParentManifest(raw, {
|
||||
runAttempt: 2,
|
||||
runId: "29090000000",
|
||||
});
|
||||
const selected = requiredChildKeysForRerunGroup(manifest.rerunGroup, manifest.validationInputs);
|
||||
expect([...selected].sort()).toEqual([
|
||||
"normalCi",
|
||||
"npmTelegram",
|
||||
"pluginPrerelease",
|
||||
"productPerformance",
|
||||
"releaseChecks",
|
||||
]);
|
||||
const missing = {
|
||||
...manifest,
|
||||
childRunIds: { ...manifest.childRunIds, npmTelegram: "" },
|
||||
};
|
||||
expect(() =>
|
||||
manifestChildEntries(
|
||||
missing,
|
||||
expectedChildDispatches(manifest.runId, manifest.runAttempt, "main"),
|
||||
selected,
|
||||
),
|
||||
).toThrow("selected child is missing from manifest: NPM Telegram Beta E2E");
|
||||
});
|
||||
|
||||
it("keeps historical non-reuse v2 manifests readable without validation inputs", () => {
|
||||
const legacy = rawManifest({});
|
||||
delete (legacy as { validationInputs?: unknown }).validationInputs;
|
||||
@@ -1573,7 +1606,10 @@ describe("release CI summary child correlation", () => {
|
||||
);
|
||||
const mismatchedRoot = {
|
||||
...root,
|
||||
validationInputs: { ...root.validationInputs, provider: "anthropic" },
|
||||
validationInputs: {
|
||||
...root.validationInputs,
|
||||
npmTelegramScenario: "telegram-status-command",
|
||||
},
|
||||
};
|
||||
|
||||
expect(() => validateEvidenceReuseChain(current, mismatchedRoot, mismatchedRoot)).toThrow(
|
||||
|
||||
Reference in New Issue
Block a user