Fold the echo-only release joins into their public writers

candidate_qualification and release_readiness only restated other jobs'
results. publish_release_tag, publish_docker, publish_helm_chart and
activate_release now each carry the exact candidate predicate in their own
needs and if, guarded by !cancelled() with the same allowed integration
skip, and activation joins the tag and both registry publications directly.
The commit verdict restates every candidate result in place of the
readiness join.

recover-release-activation.yml accepts both shapes. A source run that has
release_readiness still has to prove only that join, as before. A run
without it has to prove publish_release_tag and at least one, and only
successful, jobs under the publish_docker and publish_helm_chart caller IDs.

Failure diagnostics and the inspection-only Helm chart artifact, which
nothing downloads, are kept for three days instead of 14 and 90.
This commit is contained in:
courtmanr@gmail.com
2026-10-01 21:47:58 +01:00
parent 6448ea3816
commit 6ce41ae9d4
8 changed files with 346 additions and 131 deletions
@@ -1077,9 +1077,10 @@ func TestCreateReleaseUploadsPowerShellInstaller(t *testing.T) {
if !strings.Contains(installSmokeJob, "contents: write") {
t.Fatal("create-release.yml install_sh_smoke must grant contents: write so the called workflow can read unpublished draft assets")
}
qualificationJob := workflowJobBlock(t, workflow, "candidate_qualification")
if !strings.Contains(qualificationJob, publishedReleaseGuard) {
t.Fatal("candidate qualification must skip historical backfill and draft-only runs")
for _, job := range []string{"publish_release_tag", "publish_docker", "activate_release"} {
if !strings.Contains(workflowJobBlock(t, workflow, job), publishedReleaseGuard) {
t.Fatalf("public writer %s must carry the candidate predicate that skips historical backfill and draft-only runs", job)
}
}
for _, job := range []string{"promote_floating_tags", "publish_helm_pages", "promote_private_pro_runtime", "update_stable_demo"} {
if strings.Contains(workflow, "\n "+job+":\n") {
@@ -3433,8 +3434,7 @@ func TestReleasePipelinePromotesOneImmutableCandidate(t *testing.T) {
integrationJob := workflowJobBlock(t, createWorkflow, "integration_tests")
validationJob := workflowJobBlock(t, createWorkflow, "validate_release_assets")
privateStageJob := workflowJobBlock(t, createWorkflow, "stage_private_pro_runtime")
qualificationJob := workflowJobBlock(t, createWorkflow, "candidate_qualification")
readinessJob := workflowJobBlock(t, createWorkflow, "release_readiness")
qualificationJob := workflowJobBlock(t, createWorkflow, "publish_release_tag")
dispatchJob := workflowJobBlock(t, createWorkflow, "dispatch_release_convergence")
activationJob := workflowJobBlock(t, createWorkflow, "activate_release")
commitVerdictJob := workflowJobBlock(t, createWorkflow, "release_commit_verdict")
@@ -3580,11 +3580,28 @@ func TestReleasePipelinePromotesOneImmutableCandidate(t *testing.T) {
t.Fatalf("build-release-candidate.yml missing single-build contract: %s", needle)
}
}
for _, jobName := range []string{"publish_release_tag", "publish_docker", "publish_helm_chart"} {
for _, removedJoin := range []string{"candidate_qualification", "release_readiness"} {
if strings.Contains(createWorkflow, "\n "+removedJoin+":\n") ||
strings.Contains(createWorkflow, "needs."+removedJoin+".") {
t.Fatalf("create-release.yml must not reintroduce the echo-only %s join", removedJoin)
}
}
for _, jobName := range []string{"publish_release_tag", "publish_docker", "publish_helm_chart", "activate_release"} {
job := workflowJobBlock(t, createWorkflow, jobName)
if !strings.Contains(job, "- candidate_qualification") ||
!strings.Contains(job, "needs.candidate_qualification.result == 'success'") {
t.Fatalf("public writer %s must require successful candidate qualification", jobName)
for _, dependency := range []string{
"publication_trust_preflight", "build_release_candidate", "qualify_release_containers",
"frontend_bundle", "frontend_checks", "windows_install_command_smoke", "backend_tests",
"release_smoke", "create_release", "validate_release_assets", "install_sh_smoke",
} {
if !strings.Contains(job, "- "+dependency) ||
!strings.Contains(job, "needs."+dependency+".result == 'success'") {
t.Fatalf("public writer %s must require successful candidate check %s itself", jobName, dependency)
}
}
if !strings.Contains(job, "!cancelled() && needs.prepare.result == 'success'") ||
!strings.Contains(job, "(needs.integration_tests.result == 'success' || needs.integration_tests.result == 'skipped')") ||
!strings.Contains(job, "needs.stage_private_pro_runtime.result == 'success'") {
t.Fatalf("public writer %s must carry the complete cancellation-safe candidate predicate", jobName)
}
}
publishDockerJob := workflowJobBlock(t, createWorkflow, "publish_docker")
@@ -3656,11 +3673,11 @@ func TestReleasePipelinePromotesOneImmutableCandidate(t *testing.T) {
}
}
for _, dependency := range []string{
"candidate_qualification", "publish_release_tag", "publish_docker", "publish_helm_chart",
"publish_release_tag", "publish_docker", "publish_helm_chart",
} {
if !strings.Contains(readinessJob, "- "+dependency) ||
!strings.Contains(readinessJob, "needs."+dependency+".result == 'success'") {
t.Fatalf("release readiness must require successful %s", dependency)
if !strings.Contains(activationJob, "- "+dependency) ||
!strings.Contains(activationJob, "needs."+dependency+".result == 'success'") {
t.Fatalf("release activation must require successful %s", dependency)
}
}
@@ -3674,8 +3691,8 @@ func TestReleasePipelinePromotesOneImmutableCandidate(t *testing.T) {
"- promote_private_pro_runtime",
"- update_stable_demo",
} {
if strings.Contains(readinessJob, forbiddenDependency) {
t.Fatalf("immutable readiness must exclude mutable customer state: %s", forbiddenDependency)
if strings.Contains(activationJob, forbiddenDependency) {
t.Fatalf("immutable activation must exclude mutable customer state: %s", forbiddenDependency)
}
}
for _, dependency := range []string{"- create_release", "- stage_private_pro_runtime"} {
@@ -3683,8 +3700,8 @@ func TestReleasePipelinePromotesOneImmutableCandidate(t *testing.T) {
t.Fatalf("durable convergence dispatch missing staged dependency: %s", dependency)
}
}
if strings.Contains(dispatchJob, "- release_readiness") {
t.Fatal("durable convergence dispatch must prewarm before the readiness join")
if strings.Contains(dispatchJob, "- publish_release_tag") {
t.Fatal("durable convergence dispatch must prewarm before public publication")
}
if !strings.Contains(dispatchJob, "github.event.inputs.draft_only != 'true'") ||
!strings.Contains(dispatchJob, "historical_asset_backfill_only != 'true'") {
@@ -4250,7 +4267,7 @@ func TestReleaseCutGatesCriticalFrontendAndWindowsRuntimeProof(t *testing.T) {
windowsJob := workflowJobBlock(t, workflow, "windows_install_command_smoke")
smokeJob := workflowJobBlock(t, workflow, "release_smoke")
createJob := workflowJobBlock(t, workflow, "create_release")
qualificationJob := workflowJobBlock(t, workflow, "candidate_qualification")
qualificationJob := workflowJobBlock(t, workflow, "publish_release_tag")
verdictJob := workflowJobBlock(t, workflow, "release_commit_verdict")
for _, needle := range []string{
@@ -495,7 +495,6 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
publication_preflight = workflow_job_block(
workflow, "publication_trust_preflight"
)
readiness = workflow_job_block(workflow, "release_readiness")
dispatch = workflow_job_block(workflow, "dispatch_release_convergence")
activation = workflow_job_block(workflow, "activate_release")
commit_verdict = workflow_job_block(workflow, "release_commit_verdict")
@@ -523,10 +522,12 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
early_job = workflow_job_block(workflow, early_job_name)
self.assertIn("- publication_trust_preflight", early_job)
for dependency in (
"candidate_qualification", "publish_release_tag", "publish_docker", "publish_helm_chart",
):
self.assertIn(f"- {dependency}", readiness)
# The echo-only joins are gone: activation joins the tag and registry
# publications directly.
self.assertNotRegex(workflow, r"(?m)^ (candidate_qualification|release_readiness):$")
for dependency in ("publish_release_tag", "publish_docker", "publish_helm_chart"):
self.assertIn(f"- {dependency}", activation)
self.assertIn(f"needs.{dependency}.result == 'success'", activation)
for mutable_job in (
"publish_helm_pages",
"promote_floating_tags",
@@ -534,23 +535,21 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
"update_stable_demo",
):
with self.subTest(mutable_job=mutable_job):
self.assertNotIn(f"- {mutable_job}", readiness)
self.assertNotIn(f"- {mutable_job}", activation)
self.assertNotRegex(workflow, rf"(?m)^ {mutable_job}:$")
mutable = workflow_job_block(convergence, mutable_job)
self.assertIn("needs: acquire_customer_promotion_lease", mutable)
self.assertIn("- release_readiness", activation)
self.assertIn(
"needs.publication_trust_preflight.result == 'success'",
workflow_job_block(workflow, "candidate_qualification")
workflow_job_block(workflow, "publish_release_tag")
)
self.assertIn("- publication_trust_preflight", commit_verdict)
self.assertIn(
'require_result "publication trust preflight"', commit_verdict
)
self.assertIn("- dispatch_release_convergence", activation)
self.assertNotIn("- release_readiness", dispatch)
self.assertNotIn("- publish_release_tag", dispatch)
self.assertIn("- create_release", dispatch)
self.assertIn("- stage_private_pro_runtime", dispatch)
self.assertIn("github.event.inputs.draft_only != 'true'", dispatch)
@@ -650,6 +649,105 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
self.assertNotIn("release_id:", demo_workflow)
self.assertNotIn("unpublished draft", demo_workflow)
def test_activation_recovery_accepts_runs_with_and_without_readiness_join(self) -> None:
# Execute the recovery's source-job qualification against job listings
# in both create-release shapes. Old-shape runs must recover exactly as
# before; joinless runs must prove the publication jobs the join used
# to require. Job names mirror a real v6.4 release run.
job = workflow_job_block(read(".github/workflows/recover-release-activation.yml"), "recover_activation")
start = job.index("immutable_join=release_readiness")
end = job.index('gh api "repos/${GITHUB_REPOSITORY}/releases?per_page=100"')
script = "set -euo pipefail\n" + job[start:end] + "echo QUALIFIED\n"
common = [
("prepare", "success"), ("create_release", "success"),
("dispatch_release_convergence", "success"), ("backend_tests", "success"),
("integration_tests", "skipped"),
("publish_release_tag", "success"),
("publish_docker / Publish server image", "success"),
("publish_docker / Publish control-plane image", "success"),
("publish_docker / Verify exact image identities and provenance", "success"),
("publish_helm_chart / Package and Push Helm Chart", "success"),
("activate_release", "failure"),
("Release Activation Commit Verdict", "failure"),
]
def qualifies(jobs: list[tuple[str, str]]) -> bool:
with tempfile.TemporaryDirectory() as temp:
listing = Path(temp) / "jobs.json"
listing.write_text(json.dumps([
{"name": name, "status": "completed", "conclusion": conclusion}
for name, conclusion in jobs
]))
result = subprocess.run(
["bash", "-c", script], env=os.environ | {"source_jobs": str(listing)},
text=True, capture_output=True,
)
self.assertEqual(result.returncode == 0, "QUALIFIED" in result.stdout, result.stderr)
return result.returncode == 0
def without(jobs: list[tuple[str, str]], prefix: str) -> list[tuple[str, str]]:
return [item for item in jobs if not item[0].startswith(prefix)]
def replaced(jobs: list[tuple[str, str]], name: str, conclusion: str) -> list[tuple[str, str]]:
return [(item[0], conclusion if item[0] == name else item[1]) for item in jobs]
old_shape = [("candidate_qualification", "success"), ("release_readiness", "success"), *common]
self.assertTrue(qualifies(old_shape))
# The old shape trusts only its join, as before, so reusable display
# names are never consulted for it.
self.assertTrue(qualifies(without(without(old_shape, "publish_docker"), "publish_helm_chart")))
self.assertFalse(qualifies(replaced(old_shape, "release_readiness", "skipped")))
self.assertFalse(qualifies(replaced(old_shape, "dispatch_release_convergence", "skipped")))
new_shape = common
self.assertTrue(qualifies(new_shape))
for name in ("prepare", "create_release", "dispatch_release_convergence", "publish_release_tag",
"publish_docker / Publish server image",
"publish_helm_chart / Package and Push Helm Chart"):
with self.subTest(skipped=name):
self.assertFalse(qualifies(replaced(new_shape, name, "skipped")))
for prefix in ("publish_release_tag", "publish_docker", "publish_helm_chart"):
with self.subTest(missing=prefix):
self.assertFalse(qualifies(without(new_shape, prefix)))
# A skipped reusable caller is listed under its bare job ID.
for name in ("publish_docker", "publish_helm_chart"):
with self.subTest(skipped_caller=name):
self.assertFalse(qualifies([*without(new_shape, name), (name, "skipped")]))
# Failures outside the activation boundary still reject either shape.
self.assertFalse(qualifies(replaced(new_shape, "backend_tests", "failure")))
self.assertFalse(qualifies(replaced(old_shape, "backend_tests", "failure")))
def test_commit_verdict_restates_the_candidate_predicate(self) -> None:
# The verdict no longer reads a readiness join, so it must reject every
# candidate failure itself. Run its result checks with each outcome.
verdict = workflow_job_block(read(".github/workflows/create-release.yml"), "release_commit_verdict")
step = yaml.safe_load("jobs:\n" + verdict)["jobs"]["release_commit_verdict"]["steps"][-1]
script = step["run"]
script = script[:script.index("./scripts/verify-github-release-integrity.sh")] + "exit 0\nfi\n"
results = {
"PUBLICATION_TRUST_RESULT", "SMOKE_RESULT", "WINDOWS_INSTALL_COMMAND_RESULT",
"CREATE_RESULT", "VALIDATE_RESULT", "DOCKER_RESULT", "INSTALL_RESULT", "HELM_RESULT",
"BUILD_CANDIDATE_RESULT", "CONTAINER_QUALIFICATION_RESULT", "FRONTEND_BUNDLE_RESULT",
"FRONTEND_CHECKS_RESULT", "BACKEND_RESULT", "INTEGRATION_RESULT", "TAG_RESULT",
"CONVERGENCE_DISPATCH_RESULT", "PRIVATE_PRO_STAGE_RESULT",
}
self.assertTrue(results <= set(step["env"]))
self.assertNotIn("READINESS_RESULT", step["env"])
good = dict.fromkeys(results, "success") | {"DRAFT_ONLY": "false", "VERSION": "6.6.0"}
def passes(env: dict[str, str]) -> bool:
result = subprocess.run(["bash", "-c", script], env=os.environ | env, text=True, capture_output=True)
return result.returncode == 0
self.assertTrue(passes(good))
self.assertTrue(passes(good | {"INTEGRATION_RESULT": "skipped"}))
for name in sorted(results):
for state in ("failure", "cancelled", "skipped"):
if name == "INTEGRATION_RESULT" and state == "skipped":
continue
with self.subTest(result=name, state=state):
self.assertFalse(passes(good | {name: state}))
def test_activation_recovery_reuses_the_qualified_candidate_without_rebuilding(self) -> None:
release = read(".github/workflows/create-release.yml")
recovery = read(".github/workflows/recover-release-activation.yml")
@@ -661,6 +759,9 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
self.assertIn("release_readiness", job)
self.assertIn("dispatch_release_convergence", job)
self.assertIn("release_readiness is the canonical DAG join", job)
# Runs made without the echo-only join prove its publication jobs.
self.assertIn("immutable_join=publish_release_tag", job)
self.assertIn("for reusable_job in publish_docker publish_helm_chart", job)
self.assertNotIn("docker_build", job)
self.assertNotIn("helm_smoke", job)
self.assertIn("failure outside the recoverable activation boundary", job)
@@ -2960,25 +3061,37 @@ class CandidatePublicationBoundaryTest(unittest.TestCase):
expression = re.sub(r"!(?!=)", "not ", expression)
return bool(eval(expression, {"__builtins__": {}, "startsWith": lambda value, prefix: value.startswith(prefix)}))
CANDIDATE_CHECKS = frozenset({
"prepare", "publication_trust_preflight", "build_release_candidate",
"qualify_release_containers", "frontend_bundle", "frontend_checks",
"windows_install_command_smoke", "backend_tests", "integration_tests",
"release_smoke", "create_release", "validate_release_assets",
"install_sh_smoke", "stage_private_pro_runtime",
})
PUBLIC_WRITERS = ("publish_release_tag", "publish_docker", "publish_helm_chart", "activate_release")
def test_qualified_beta_tag_survives_intentionally_skipped_ancestor(self) -> None:
outcomes = dict.fromkeys(self.jobs, "success")
outcomes["integration_tests"] = "skipped"
self.assertTrue(self.condition("candidate_qualification", outcomes))
# Actions applies implicit success() when no status function is present.
# A skipped ancestor therefore prevents the writer despite the explicit
# qualification result. Model that status gate as well as its expression.
writer = self.jobs["publish_release_tag"]
has_status = bool(re.search(r"\b(always|success|failure|cancelled)\(", writer["if"]))
self.assertTrue(has_status and self.condition("publish_release_tag", outcomes))
for dependency in writer["needs"]:
for state in ("failure", "cancelled", "skipped"):
with self.subTest(dependency=dependency, state=state):
self.assertFalse(self.condition("publish_release_tag", outcomes | {dependency: state}))
# A skipped ancestor would then prevent every writer despite the
# explicit candidate predicate. Model that status gate as well.
for name in self.PUBLIC_WRITERS:
writer = self.jobs[name]
has_status = bool(re.search(r"\b(always|success|failure|cancelled)\(", writer["if"]))
with self.subTest(writer=name):
self.assertTrue(has_status and self.condition(name, outcomes))
for dependency in writer["needs"]:
for state in ("failure", "cancelled", "skipped"):
if dependency == "integration_tests" and state == "skipped":
continue # Already skipped above, by policy.
with self.subTest(writer=name, dependency=dependency, state=state):
self.assertFalse(self.condition(name, outcomes | {dependency: state}))
def test_workflow_cancellation_blocks_completed_prerequisite_writers(self) -> None:
good = dict.fromkeys(self.jobs, "success")
for writer in ("publish_release_tag", "publish_docker", "publish_helm_chart",
"release_readiness", "dispatch_release_convergence", "activate_release"):
"dispatch_release_convergence", "activate_release"):
with self.subTest(writer=writer):
self.assertTrue(self.condition(writer, good))
# Cancellation is workflow state, not a changed needs.result:
@@ -2988,29 +3101,39 @@ class CandidatePublicationBoundaryTest(unittest.TestCase):
self.assertTrue(self.condition("release_commit_verdict", good, cancelled=True))
def test_failed_candidate_cannot_reach_any_public_version_writer(self) -> None:
required = {
"prepare", "publication_trust_preflight", "build_release_candidate",
"qualify_release_containers", "frontend_bundle", "frontend_checks",
"windows_install_command_smoke", "backend_tests", "integration_tests",
"release_smoke", "create_release", "validate_release_assets",
"install_sh_smoke", "stage_private_pro_runtime",
}
self.assertEqual(set(self.jobs["candidate_qualification"]["needs"]), required)
required = set(self.CANDIDATE_CHECKS)
# The echo-only candidate and readiness joins were folded into the
# writers. Each writer depends on, and judges, every candidate check
# itself instead of trusting a join job's result.
self.assertNotIn("candidate_qualification", self.jobs)
self.assertNotIn("release_readiness", self.jobs)
tag_predicate = self.jobs["publish_release_tag"]["if"].removesuffix("}}").strip()
self.assertEqual(set(self.jobs["publish_release_tag"]["needs"]), required)
good = dict.fromkeys(self.jobs, "success")
self.assertTrue(self.condition("candidate_qualification", good))
self.assertFalse(self.condition("candidate_qualification", good, draft=True))
for failed in required:
for writer in self.PUBLIC_WRITERS:
with self.subTest(writer=writer):
self.assertTrue(required <= set(self.jobs[writer]["needs"]))
# Every writer repeats the tag's exact candidate predicate.
self.assertTrue(self.jobs[writer]["if"].startswith(tag_predicate))
self.assertTrue(self.condition(writer, good))
self.assertFalse(self.condition(writer, good, draft=True))
for failed in required:
for state in ("failure", "cancelled", "skipped"):
if failed == "integration_tests" and state == "skipped":
continue # Existing alpha/beta policy omits integration tests.
with self.subTest(writer=writer, failed=failed, state=state):
self.assertFalse(self.condition(writer, good | {failed: state}))
# Old shape: release_readiness also required the tag and both registry
# publications before activation. The new activation predicate must
# match the old readiness-gated one for every single-job outcome.
for writer in ("publish_docker", "publish_helm_chart", "activate_release"):
for state in ("failure", "cancelled", "skipped"):
if failed == "integration_tests" and state == "skipped":
continue # Existing alpha/beta policy omits integration tests.
with self.subTest(failed=failed, state=state):
outcomes = good | {failed: state}
self.assertFalse(self.condition("candidate_qualification", outcomes))
for writer in ("publish_release_tag", "publish_docker", "publish_helm_chart"):
self.assertIn("candidate_qualification", self.jobs[writer]["needs"])
with self.subTest(writer=writer, tag=state):
self.assertFalse(self.condition(writer, good | {"publish_release_tag": state}))
for dependency in ("publish_docker", "publish_helm_chart", "dispatch_release_convergence"):
for state in ("failure", "cancelled", "skipped"):
with self.subTest(writer=writer, state=state):
self.assertFalse(self.condition(writer, good | {"candidate_qualification": state}))
with self.subTest(activation_dependency=dependency, state=state):
self.assertFalse(self.condition("activate_release", good | {dependency: state}))
# Detect accidental dependency cycles, including moving publication into
# the candidate join that publication itself must wait for.
def visit(name: str, stack: tuple[str, ...] = ()) -> None: