fix(release): require requested validation children (#118031)

* fix(release): require requested validation children

* fix(release): bind Telegram reuse evidence
This commit is contained in:
Vincent Koc
2026-08-03 02:39:59 +08:00
committed by GitHub
parent 7e5e4483c5
commit 1ec2aa8209
8 changed files with 282 additions and 53 deletions
+39 -5
View File
@@ -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")
+4 -1
View File
@@ -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,
+56 -41
View File
@@ -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"',
+37 -1
View File
@@ -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(