Skip to content

Block a partial publish when a build fails - #42

Merged
ptr727 merged 1 commit into
developfrom
feature/block-partial-publish
Aug 3, 2026
Merged

Block a partial publish when a build fails#42
ptr727 merged 1 commit into
developfrom
feature/block-partial-publish

Conversation

@ptr727

@ptr727 ptr727 commented Aug 3, 2026

Copy link
Copy Markdown
Owner

The one behavioral defect the audit found, and it is live today.

WORKFLOW.md D4.5 requires that a build failure block every publish target. This repo's WORKFLOW.md was missing the guarantee entirely, and the pipeline did not satisfy it.

The defect

build-docker:
  needs: [get-version, validate, validate-release]     # no build-executable
  if: ${{ !cancelled() && ... }}                       # cancellation only

The image builds from source and consumes no executable artifact, so the two builds were independent and nothing forced an ordering. On a real publish where build-executable fails:

Job Before Result
build-executable fails -
github-release needs it, so it skips no tag, no release
build-docker untouched, runs multi-arch image pushed, latest moves

An image shipped with no release behind it. That is exactly the partial publish D4.5 exists to prevent.

The fix

build-docker becomes the terminal publish target: it needs build-executable, and its if gains !failure().

The distinction that matters is failed versus skipped. A failed upstream build must stop the push; a skipped one must not, because validate is skipped on every smoke run and that run still has to build the image. !failure() draws exactly that line. The explicit get-version and validate-release result checks stay, so a job that somehow did not run cannot feed empty version inputs into a build.

Reasoning, since the failure path is not reachable from a PR

Smoke never publishes, so no pull request can exercise a failing real publish. The behavior is established from the needs graph rather than by observation:

Case failure() build-docker github-release
smoke PR (validate skipped) false runs, push: false skipped (!inputs.smoke)
real publish, all green false runs, pushes runs
real publish, build-executable fails true skipped skipped
real publish, validate fails true skipped skipped
real publish, validate-release fails true skipped skipped

The smoke row is the one that would break if !failure() were the wrong guard, and this PR's own CI run is that row.

Cost

On smoke, build-docker now waits for build-executable instead of running beside it, adding roughly the executable build's duration to PR feedback. Correctness over a subset of the wall clock.

D4.5 lands here, not in #40

The doc-refresh PR deliberately left it out. Stating the guarantee before implementing it would have replaced a silent gap with a false claim, so the contract and the code that satisfies it land together.

Verification

  • actionlint clean.
  • dotnet husky run clean, editorconfig-checker clean, hub prose gate clean on the changed lines.
  • The smoke path is exercised by this PR's own checks.

Last of four in the round. Follows #39, #40 and #41. If #40 merges first this may need a trivial rebase - both touch WORKFLOW.md D4, two lines apart.

🤖 Generated with Claude Code

WORKFLOW.md D4.5 requires that a build failure block every publish target.
This repo's WORKFLOW.md was missing the guarantee entirely, and the
pipeline did not satisfy it.

build-docker needed only [get-version, validate, validate-release] and
guarded solely on cancellation. The image builds from source and consumes
no executable artifact, so the two builds were independent and nothing
forced an ordering. On a real publish where build-executable failed,
github-release skipped, so no tag and no release were cut, while
build-docker was untouched and pushed the multi-arch image anyway, moving
`latest`. An image shipped with no release behind it.

build-docker becomes the terminal publish target: it needs
build-executable, and its `if` gains `!failure()`. The distinction that
matters is failed versus skipped. A failed upstream build must stop the
push, and a skipped one must not, because `validate` is skipped on every
smoke run and that run still has to build the image. The explicit
get-version and validate-release result checks stay, so a job that somehow
did not run cannot feed empty version inputs into a build.

The cost is on smoke, where build-docker now waits for build-executable
instead of running beside it, adding roughly the executable build's
duration to PR feedback. Correctness over a subset of the wall clock.

WORKFLOW.md gains D4.5 in the same commit rather than in the doc-refresh
PR ahead of it, so the contract and the code that satisfies it land
together instead of the file claiming a guarantee the pipeline breaks.

The failure path is not reachable from a pull request, since smoke never
publishes, so it is established from the needs graph rather than by
observation. actionlint passes.

Audit run 2026-08-03T16:52:36Z, hub 1ed0cc8, against develop@39c896b.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 3, 2026 17:31
@codecov

codecov Bot commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 43.37%. Comparing base (39c896b) to head (3d4b882).

Additional details and impacted files
@@           Coverage Diff            @@
##           develop      #42   +/-   ##
========================================
  Coverage    43.37%   43.37%           
========================================
  Files           28       28           
  Lines         3896     3896           
  Branches       306      306           
========================================
  Hits          1690     1690           
  Misses        2154     2154           
  Partials        52       52           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Ready to approve

The workflow dependency/guard changes correctly prevent Docker publishing on upstream build failure without breaking the smoke (skipped-validate) path described in the PR.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

This PR closes a real release/publish correctness gap by ensuring Docker publishing cannot proceed when an upstream build job fails, aligning the workflow behavior with WORKFLOW.md’s D4.5 contract.

Changes:

  • Document the “no partial publish” guarantee (D4.5) in WORKFLOW.md.
  • Update the release workflow so build-docker depends on build-executable and is gated by !failure() (while still allowing skipped smoke-only validation).
File summaries
File Description
WORKFLOW.md Adds D4.5 to explicitly require that any failed enabled build blocks all publish targets (including Docker).
.github/workflows/build-release-task.yml Makes build-docker depend on build-executable and adds !failure() to prevent Docker pushes when any upstream dependency failed, while preserving smoke behavior when validate is skipped.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

@ptr727
ptr727 merged commit 252f5c5 into develop Aug 3, 2026
14 checks passed
@ptr727
ptr727 deleted the feature/block-partial-publish branch August 18, 2026 17:19
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.

2 participants