Skip to content

Commit da81d09

Browse files
fix(ci): key release on validation run id, drop src-tree-hash gate
The release gate compared the merged main src tree against the validated tree and aborted on any mismatch. But the bump commit the release creates on main changes version.py, so the merged tree never equals the validated (pre-bump) tree, making the gate impossible to satisfy on a retry. The binary is built with the version injected at build time, so the tree difference never mattered. Remove src_tree_hash entirely. The validation records only its result and run id on the release PR; the release gate requires a passing validation exists and resolves the validated artifact by that run id. Nightly validation skips only when the last recorded run already passed.
1 parent ea0c4fc commit da81d09

4 files changed

Lines changed: 26 additions & 55 deletions

File tree

.github/workflows/release.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ jobs:
3434
msi_name: ${{ steps.run.outputs.msi_name }}
3535
artifact_id: ${{ steps.run.outputs.artifact_id }}
3636
validation_run_id: ${{ steps.gate.outputs.validation_run_id }}
37-
src_tree_hash: ${{ steps.gate.outputs.src_tree_hash }}
3837
steps:
3938
- uses: actions/checkout@v7
4039
with:
@@ -80,7 +79,7 @@ jobs:
8079
- name: Download the validated binaries
8180
uses: actions/download-artifact@v4
8281
with:
83-
name: validated_${{ needs.init.outputs.src_tree_hash }}
82+
name: validated_${{ needs.init.outputs.validation_run_id }}
8483
path: artifacts
8584
github-token: ${{ secrets.GITHUB_TOKEN }}
8685
run-id: ${{ needs.init.outputs.validation_run_id }}

.github/workflows/scripts/actions.py

Lines changed: 16 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -686,7 +686,6 @@ def make_msi(self) -> None:
686686

687687

688688
class ReleaseValidation:
689-
SOURCE_TREE = "src/subsearch"
690689
MARKER = "release-validation"
691690
RESULT_PASSED = "passed"
692691
RESULT_FAILED = "failed"
@@ -696,10 +695,8 @@ class ReleaseValidation:
696695
BODY_BLOCK_START = "<!-- release-validation:start -->"
697696
BODY_BLOCK_END = "<!-- release-validation:end -->"
698697

699-
# Matches the marker HTML comment: <!-- release-validation:result=passed;src=<hash>;run=<id> -->
700-
_STATE_PATTERN = re.compile(
701-
r"<!--\s*release-validation:result=(?P<result>\w+);src=(?P<src>[0-9a-f]+);run=(?P<run>\d+)\s*-->"
702-
)
698+
# Matches the marker HTML comment: <!-- release-validation:result=passed;run=<id> -->
699+
_STATE_PATTERN = re.compile(r"<!--\s*release-validation:result=(?P<result>\w+);run=(?P<run>\d+)\s*-->")
703700

704701
def open_release_pr(self) -> str | None:
705702
completed = subprocess.run(
@@ -727,25 +724,7 @@ def open_release_pr(self) -> str | None:
727724
number = completed.stdout.strip()
728725
return number or None
729726

730-
def src_tree_hash(self, commit: str = "HEAD") -> str:
731-
completed = subprocess.run(
732-
["git", "rev-parse", f"{commit}:{self.SOURCE_TREE}"],
733-
check=True,
734-
capture_output=True,
735-
text=True,
736-
)
737-
return completed.stdout.strip()
738-
739-
def pr_head_sha(self, number: str) -> str:
740-
completed = subprocess.run(
741-
["gh", "pr", "view", number, "--json", "headRefOid", "--jq", ".headRefOid"],
742-
check=True,
743-
capture_output=True,
744-
text=True,
745-
)
746-
return completed.stdout.strip()
747-
748-
def last_validation(self, number: str) -> tuple[str, str, str] | None:
727+
def last_validation(self, number: str) -> tuple[str, str] | None:
749728
completed = subprocess.run(
750729
["gh", "pr", "view", number, "--json", "comments", "--jq", ".comments[].body"],
751730
check=True,
@@ -759,23 +738,21 @@ def last_validation(self, number: str) -> tuple[str, str, str] | None:
759738
match = found
760739
if match is None:
761740
return None
762-
return match.group("src"), match.group("result"), match.group("run")
741+
return match.group("result"), match.group("run")
763742

764743
def should_validate(self, number: str) -> bool:
765744
previous = self.last_validation(number)
766745
if previous is None:
767746
return True
768-
validated_src, result, _ = previous
769-
if result != self.RESULT_PASSED:
770-
return True
771-
return validated_src != self.src_tree_hash(self.pr_head_sha(number))
747+
result, _ = previous
748+
return result != self.RESULT_PASSED
772749

773-
def _marker_body(self, src_tree_hash: str, result: str, run_id: str) -> str:
774-
comment = f"<!-- {self.MARKER}:result={result};src={src_tree_hash};run={run_id} -->"
750+
def _marker_body(self, result: str, run_id: str) -> str:
751+
comment = f"<!-- {self.MARKER}:result={result};run={run_id} -->"
775752
passed = result == self.RESULT_PASSED
776753
validation_outcome = "passed" if passed else "failed"
777754
status = f"Release validation {validation_outcome}"
778-
return f"{comment}\n**{status}** — src tree `{src_tree_hash}` (run {run_id})"
755+
return f"{comment}\n**{status}** (run {run_id})"
779756

780757
def _has_marker_comment(self, number: str) -> bool:
781758
completed = subprocess.run(
@@ -787,9 +764,9 @@ def _has_marker_comment(self, number: str) -> bool:
787764
comments = json.loads(completed.stdout)["comments"]
788765
return any(f"<!-- {self.MARKER}:" in comment["body"] for comment in comments)
789766

790-
def _body_block(self, src_tree_hash: str, result: str, run_id: str) -> str:
767+
def _body_block(self, result: str, run_id: str) -> str:
791768
outcome = "passed" if result == self.RESULT_PASSED else "failed"
792-
line = f"###### Release validation {outcome} — src tree `{src_tree_hash}` (run {run_id})"
769+
line = f"###### Release validation {outcome} (run {run_id})"
793770
return "\n".join([self.BODY_BLOCK_START, line, self.BODY_BLOCK_END])
794771

795772
def _replace_body_block(self, body: str, block: str) -> str:
@@ -801,22 +778,22 @@ def _replace_body_block(self, body: str, block: str) -> str:
801778
return f"{block}\n\n{body}"
802779
return body[:start] + block + body[end + len(self.BODY_BLOCK_END) :]
803780

804-
def _update_body(self, number: str, src_tree_hash: str, result: str, run_id: str) -> None:
781+
def _update_body(self, number: str, result: str, run_id: str) -> None:
805782
completed = subprocess.run(
806783
["gh", "pr", "view", number, "--json", "body", "--jq", ".body"],
807784
check=True,
808785
capture_output=True,
809786
text=True,
810787
)
811788
existing_body = completed.stdout.rstrip("\n")
812-
block = self._body_block(src_tree_hash, result, run_id)
789+
block = self._body_block(result, run_id)
813790
new_body = self._replace_body_block(existing_body, block)
814791
subprocess.run(["gh", "pr", "edit", number, "--body", new_body], check=True)
815792

816-
def record_validation(self, number: str, src_tree_hash: str, result: str, run_id: str) -> None:
817-
body = self._marker_body(src_tree_hash, result, run_id)
793+
def record_validation(self, number: str, result: str, run_id: str) -> None:
794+
body = self._marker_body(result, run_id)
818795
command = ["gh", "pr", "comment", number, "--body", body]
819796
if self._has_marker_comment(number):
820797
command.append("--edit-last")
821798
subprocess.run(command, check=True)
822-
self._update_body(number, src_tree_hash, result, run_id)
799+
self._update_body(number, result, run_id)

.github/workflows/scripts/jobs.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,6 @@ def run(self) -> None:
356356

357357
step_summary.set_output("proceed", "true" if proceed else "false")
358358
step_summary.set_output("pr_number", number)
359-
step_summary.set_output("src_tree_hash", validation.src_tree_hash(validation.pr_head_sha(number)))
360359

361360

362361
class ValidateRecord:
@@ -366,15 +365,14 @@ class ValidateRecord:
366365
def run(self) -> None:
367366
validation = ReleaseValidation()
368367
number = os.environ["PR_NUMBER"]
369-
src_tree_hash = os.environ["SRC_TREE_HASH"]
370368
run_id = os.environ["GITHUB_RUN_ID"]
371369

372370
required_passed = all(os.environ.get(name) == "success" for name in self._REQUIRED_RESULTS)
373371
# Binary tests are optional (skip_binary_tests dispatch input); a skipped run is acceptable, a failed one is not.
374372
tests_passed = os.environ.get("TEST_RESULT") in self._ACCEPTABLE
375373
passed = required_passed and tests_passed
376374
result = validation.RESULT_PASSED if passed else validation.RESULT_FAILED
377-
validation.record_validation(number, src_tree_hash, result, run_id)
375+
validation.record_validation(number, result, run_id)
378376

379377

380378
class ReleaseGate:
@@ -383,16 +381,15 @@ def run(self) -> None:
383381
step_summary = StepSummary()
384382

385383
number = os.environ["PR_NUMBER"]
386-
merge_src_tree_hash = validation.src_tree_hash("HEAD")
387384
previous = validation.last_validation(number)
388385

389-
if previous is None or previous[1] != validation.RESULT_PASSED or previous[0] != merge_src_tree_hash:
390-
step_summary.card(f"No validated artifact matching src tree {merge_src_tree_hash}", passed=False)
391-
raise SystemExit(f"No validated artifact matching src tree {merge_src_tree_hash}")
386+
if previous is None or previous[0] != validation.RESULT_PASSED:
387+
step_summary.card("No passing validation found for the release pull request", passed=False)
388+
raise SystemExit("No passing validation found for the release pull request")
392389

393-
step_summary.set_output("src_tree_hash", merge_src_tree_hash)
394-
step_summary.set_output("validation_run_id", previous[2])
395-
step_summary.card(f"Validated artifact found for src tree {merge_src_tree_hash}", passed=True)
390+
_, validation_run_id = previous
391+
step_summary.set_output("validation_run_id", validation_run_id)
392+
step_summary.card(f"Validated artifact found (run {validation_run_id})", passed=True)
396393

397394

398395
class ReadValidatedHashes:

.github/workflows/validate-release.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ jobs:
3737
outputs:
3838
proceed: ${{ steps.run.outputs.proceed }}
3939
pr_number: ${{ steps.run.outputs.pr_number }}
40-
src_tree_hash: ${{ steps.run.outputs.src_tree_hash }}
4140
steps:
4241
- uses: actions/checkout@v7
4342
with:
@@ -106,7 +105,7 @@ jobs:
106105

107106
- uses: actions/upload-artifact@v4
108107
with:
109-
name: validated_${{ needs.gate.outputs.src_tree_hash }}
108+
name: validated_${{ github.run_id }}
110109
path: artifacts
111110
if-no-files-found: error
112111
retention-days: ${{ env.ARTIFACT_RETENTION_DAYS }}
@@ -126,7 +125,7 @@ jobs:
126125

127126
- uses: actions/download-artifact@v4
128127
with:
129-
name: validated_${{ needs.gate.outputs.src_tree_hash }}
128+
name: validated_${{ github.run_id }}
130129
path: artifacts
131130

132131
- uses: ./.github/actions/install-test-deps
@@ -186,7 +185,6 @@ jobs:
186185
env:
187186
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
188187
PR_NUMBER: ${{ needs.gate.outputs.pr_number }}
189-
SRC_TREE_HASH: ${{ needs.gate.outputs.src_tree_hash }}
190188
INIT_RESULT: ${{ needs.init.result }}
191189
BUILD_RESULT: ${{ needs.build_binaries.result }}
192190
TEST_RESULT: ${{ needs.test_binaries.result }}

0 commit comments

Comments
 (0)