Skip to content

fix(release): verify-only re-run mode and SemVer-aware Latest (#47) - #51

Merged
DVOpenLabs merged 2 commits into
mainfrom
fix/47-release-verify-only
Sep 28, 2026
Merged

DVOpenLabs merged 2 commits into
mainfrom
fix/47-release-verify-only

Conversation

@DVOpenLabs

@DVOpenLabs DVOpenLabs commented Sep 28, 2026 •

Copy link
Copy Markdown
Owner

Closes #47.

What this changes

release-publish.yml gains three things. Nothing here publishes a release; this PR only changes the workflow and its verifier.

A. Verify-only mode (workflow_dispatch, verify_only: true)

A strictly read-only re-check of an already-public release, safe to run any number of times.

  • Needs an existing tag and an existing release; resolves the expected metadata from the tag.
  • The publish job does not run at all (if: github.event_name == 'push' || inputs.verify_only != true); verify runs with permissions: contents: read.
  • The verifier only calls gh api --method GET and gh release download. It cannot create a draft, publish, edit release state, upload assets, create/move/delete tags, or delete a release. Structural tests enforce this on the verify job.
  • verify_only runs have their own concurrency group, so a re-check never queues behind (or blocks) a publish.

B. SemVer-aware Latest

The workflow no longer forces --latest on every publish. publish_verify.py latest compares the tag against every other non-draft, non-prerelease release using SemVer 2.0 precedence and passes an explicit --latest=true|false to gh release edit. The verifier is told the same expectation. It does not assume a new release must be Latest: when it is not expected to be Latest, it checks that /releases/latest points at a release with higher precedence.

Covered by tests: v1.10.0 > v1.9.9, v2.0.0 > v1.99.99, v1.2.1 < v1.3.0 (publishing v1.2.1 while v1.3.0 exists keeps v1.3.0 as Latest), prerelease identifiers, build metadata, and unparseable tags (fail closed before any draft exists).

C. State and hash diagnostics, both always run

publish_verify.py verify runs the state check (public, not prerelease, exactly the expected assets, Latest as expected; bounded retry for GitHub's read-after-write delay) and the re-download-and-re-hash check independently. It prints one combined report covering all four combinations (state pass/fail × hashes pass/fail) and fails at the end if either failed. Failure messages name the case: A (release missing/wrong), B (release correct, Latest not yet as expected), C (API unreadable). The verifier never republishes or retries a publish.

Verification now runs in its own verify job with its own time budget. The verifier enforces an end-to-end deadline that fits inside that job's timeout, so the report is always printed.

Safety notes from review

  • Commands expected to fail under set -e capture their status with rc=0; cmd || rc=$? before any diagnostic prints.
  • gh release view is not banned globally. It stays in the read-only places that legitimately use it; tests forbid it only where mutation or refusal behaviour must be excluded.
  • Dispatch inputs (tag) are validated before anything is written to $GITHUB_ENV / $GITHUB_OUTPUT, and no ${{ }} expressions are interpolated into run: scripts.
  • Draft visibility: the read-only verify job uses releases/tags/{tag}, which never returns drafts, so a leftover draft reads as "missing" (case A). It does not depend on draft listing. Only the refuse-if-exists check lists drafts, and it runs in the contents: write publish job. GitHub documents that draft listings go to tokens with push access. This relies on that documented behaviour; it was not live-tested, because doing so would require creating a draft.

Known limitation (tracked separately)

The step that records "a publish was attempted" and the flip to public run in the same job. If the runner machine is lost after GitHub accepts the flip but before the job finishes, GitHub never receives the job's outputs and verify is skipped. On main today verification already runs inside that same job, so this is not a regression. The recovery is a verify_only dispatch, documented in docs/RELEASE_CHECKLIST.md §9. Follow-up issue: #50.

Verification

  • py -3.13 -m pytest backend/tests/test_publish_verify.py -q: 115 passed
  • actionlint .github/workflows/release-publish.yml: clean
  • git diff --check: clean
  • Exact if: equality between the arm and flip steps was mutation-tested (&& false / || true both fail the test).
  • Reviews: Opus and Sol on each round; final round verdicts: Opus APPROVE-WITH-NOTES and Sol APPROVE-WITH-NOTES on the final frozen diff (both accept the release: run the flip-to-public in its own job so a lost runner cannot skip post-publish verification #50 classification).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a read-only option to verify a release without publishing it.
    • Release verification now checks that the release is public, correctly positioned among releases, and includes the expected assets.
  • Reliability
    • Publishing and follow-up verification are coordinated to reduce conflicting release operations. Verification can also run after an interrupted or unsuccessful publish attempt.
  • Documentation
    • Updated the release checklist with verification, cancellation, and recovery guidance.

DVOpenLabs and others added 2 commits September 28, 2026 14:36
…ostics (#47)

Checkpoint of the reviewed round-4 state (frozen-47-r4, identical bytes)
recovered from the ss-wt-47 worktree after a PC crash. Not final:
Sol r4 has one open HIGH (arm/flip same job under runner loss) and
round 5 is pending.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lip if: test (#47)

Round 5 of #47 review. Removes the claim that post-publish verification
still runs if the runner dies: job outputs are only delivered at job
finalization, so a lost runner after the flip skips `verify`. Documents
the limitation and the read-only `verify_only` recovery in the workflow
comments and RELEASE_CHECKLIST.md section 9. The durable fix (flip in its
own job) is tracked in #50. No workflow logic changed (YAML parses equal).

The arm/flip `if:` test now compares full `if:` lines for exact equality;
mutation-verified (`&& false` on the arm, `|| true` on the flip both fail).

Verified: py -3.13 -m pytest backend/tests/test_publish_verify.py -q
-> 115 passed; actionlint clean; git diff --check clean.
Reviews on frozen-47-r5 (12f7a596): Opus APPROVE-WITH-NOTES,
Sol APPROVE-WITH-NOTES (both accept the #50 FOLLOWUP_DEBT classification).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: 10287602-3052-4897-a75d-2cde24f9abbc

📥 Commits

Reviewing files that changed from the base of the PR and between 95fd031 and 0abace8.

📒 Files selected for processing (4)
  • .github/workflows/release-publish.yml
  • backend/tests/test_publish_verify.py
  • docs/RELEASE_CHECKLIST.md
  • tools/release/publish_verify.py

Included review availability: This review used your included allowance. 7 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 8 reviews per hour.


📝 Walkthrough

Walkthrough

The release workflow adds a read-only verify_only dispatch and moves post-publish checks into a separate job. The verifier adds SemVer-based latest decisions, metadata-derived checksums, and deadline-bounded state and asset checks. Tests and the release checklist cover the workflow conditions and recovery paths.

Changes

Release publishing and verification

Layer / File(s) Summary
Release listing and latest decisions
tools/release/publish_verify.py, backend/tests/test_publish_verify.py
The verifier adds paginated release listing, SemVer precedence, and release-presence and latest-eligibility decisions. Tests cover precedence, pagination, and CLI outcomes.
State, metadata, and asset verification
tools/release/publish_verify.py, backend/tests/test_publish_verify.py
The verifier adds state assessments, metadata parsing, expected checksums, per-call timeouts, and a combined verification command. Tests cover outcomes, downloads, and deadline handling.
Publish gates and release publication
.github/workflows/release-publish.yml, docs/RELEASE_CHECKLIST.md, backend/tests/test_publish_verify.py
The publish job validates inputs and release artifacts, checks latest eligibility, and tracks attempted and successful publication. Workflow tests check permissions, concurrency, and publication conditions; the checklist documents publish concurrency and prerelease handling.
Separate post-publish verification
.github/workflows/release-publish.yml, docs/RELEASE_CHECKLIST.md, backend/tests/test_publish_verify.py
A read-only job verifies release state and asset hashes after an attempted publish or a verify_only dispatch. The checklist and workflow tests cover verification conditions, recovery, and cancellation behavior.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant VerifyJob as verify job
  participant TaggedCommit as tagged commit
  participant Verifier as publish_verify.py
  participant ReleasesAPI as GitHub Releases API
  VerifyJob->>TaggedCommit: Read RELEASE_METADATA.md for the tag
  VerifyJob->>Verifier: Run combined verification with expected metadata
  Verifier->>ReleasesAPI: List releases and read release state
  Verifier->>ReleasesAPI: Download release assets
  Verifier-->>VerifyJob: Return latest, state, and hash results
Loading

Merge Risk: ⚪ Minimal · up to 0abac

The release checks and read-only recovery path have no identified issue requiring a fix before merge.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.56% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 129 functions across 2 files. (2 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the two main changes: the verify-only rerun mode and SemVer-aware Latest selection.
Linked Issues check ✅ Passed The PR satisfies the coding requirements in [#47]. The workflow adds the verify_only dispatch path and a separate verification job. The verifier performs read-only checks for an existing release. La…
Out of Scope Changes check ✅ Passed The workflow, verifier, tests, and checklist changes support [#47]. The runner-loss limitation and verify_only recovery guidance document the requested rerun behavior. The durable runner-loss fix re…
Full details: Docstring Coverage

Explanation

Docstring coverage is 32.56% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 129 functions across 2 files. (2 skipped: 2 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Comment @coderabbitai help to get the list of available commands.

@DVOpenLabs
DVOpenLabs merged commit 68bc633 into main Sep 28, 2026
8 checks passed
@DVOpenLabs
DVOpenLabs deleted the fix/47-release-verify-only branch October 8, 2026 20:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

release: verify-only re-run mode, and don't force --latest when publishing an older line

1 participant