Skip to content

fix(notify): resume failed helper publication safely#117

Merged
psimaker merged 2 commits into
mainfrom
agent/helper-publication-draft-recovery
Jul 14, 2026
Merged

fix(notify): resume failed helper publication safely#117
psimaker merged 2 commits into
mainfrom
agent/helper-publication-draft-recovery

Conversation

@psimaker

@psimaker psimaker commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Summary

  • resolve draft releases through GraphQL database IDs, then read their canonical REST objects without treating API failures as release absence
  • permit a narrowly scoped owner recovery dispatch from the current main only when it binds to the failed tag-bound publication run, source tag, source commit, failed step, and skipped rollout/finalization jobs
  • check out the immutable source commit for every publication-content job and prohibit image, provenance, and SBOM-attestation writes during recovery
  • serialize normal and recovery dispatches by release tag and extend the executable publication safety policy

Failed publication state being recovered

  • source tag: notify-v2.0.0
  • source commit: 664a896c5c14b86a806f8fa47b0d900b32649bff
  • failed run: 29324314809
  • failed step: Stage Release Binaries / Create or verify the exact draft release and assets
  • root cause: GitHub created draft release 353712477, but the REST release-by-tag endpoint returned 404 for that draft
  • image publication, vulnerability/secret scan, SPDX SBOM, image provenance, image SBOM attestation, reproducible binary build, binary scan, binary provenance, and binary SBOM attestations completed successfully before the failure
  • rollout, release finalization, and public verification were skipped; the draft has no assets and remains unpublished

Safety invariants

  • the existing source tag is never moved or replaced
  • main-ref recovery requires an owner-triggered failed run whose tag, head SHA, workflow path, failed job/step, successful image job, and skipped downstream jobs all match
  • the recovery workflow definition must be the freshly fetched, verified origin/main
  • notify/release.json must be byte-identical between the recovery workflow commit and the source tag
  • a recovery aborts if the exact version image or any tag-/commit-/workflow-bound image or binary attestation is missing
  • public release retries remain read-only; existing assets are reused only on exact SHA-256 equality
  • no latest tag is read or moved by the workflow
  • upload, download, and roundtrip product evidence remain unset

Validation

  • ruby -c .github/scripts/notify-publish-safety.rb
  • ruby .github/scripts/notify-publish-safety.rb
  • Actionlint 1.7.12 with ShellCheck 0.10.0
  • Zizmor 1.26.1 offline: no findings
  • cd notify && go test ./... -count=1
  • Trivy 0.70.0 filesystem vulnerability/secret scan: 0 HIGH/CRITICAL findings
  • exact recovery-gate dry-run accepted run 29324314809 and rejected an unrelated successful run
  • GraphQL release-ID resolver found draft 353712477 and failed closed on a simulated API/repository failure
  • all five Go 1.26.5 source-tag binaries reproduced byte-identically and verified against existing provenance and SPDX attestations
  • image sha256:9718196b97afb6aee43c4578c1fb130a7874c34115f04b22c690b7d243667fc7 verified against existing provenance and SPDX attestations
  • GHCR latest remained sha256:90d4a56b0e7d5bf90541157c13b0265198df6893accf8e64b228587996e41fa4

Compatibility and scope

This changes only publication orchestration and its executable safety policy. Helper runtime behavior, wire formats, Decision 024, credentials, pairing, namespace handling, existing-user behavior, upload/download/roundtrip semantics, backup/versioning/conflict/tombstone behavior, and Relay/App runtime are unchanged.

Resolve draft releases through GraphQL release IDs so staged assets remain visible without treating API failures as absence.

Allow an owner-only main-ref recovery only when it binds to the failed tag workflow and source commit, while forbidding image and attestation writes and checking out the immutable source for every publication job.

Keep normal and recovery dispatches serialized per release tag and extend the publication safety policy.
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

The publish workflow now supports recovery dispatches from main using recovery_run_id. Publish-gate validates the failed run and exports recovery state, while downstream jobs use immutable release commits and recovery-aware artifact, attestation, release, and verification logic.

Recovery publication

Layer / File(s) Summary
Recovery policy contract
.github/scripts/notify-publish-safety.rb
Publication checks, workflow fragments, concurrency assertions, permissions, and embedded cases distinguish normal and recovery dispatches.
Workflow safety assertions
.github/scripts/notify-publish-safety.rb
Validation covers immutable source checkouts, recovery-aware reachability, fail-closed integrity checks, and GraphQL release resolution.
Publish-gate recovery validation
.github/workflows/docker.yml
Dispatch inputs and publish-gate validation identify eligible failed runs and export recovery_mode with immutable release outputs.
Recovery artifact publication
.github/workflows/docker.yml
Image and binary jobs select recovery or normal artifacts using release_sha, with provenance and SBOM verification.
Release resolution and verification
.github/workflows/docker.yml
Release creation, asset checks, rollout, finalization, and publication verification use GraphQL database-id resolution and immutable checkouts.

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

Possibly related PRs

Suggested reviewers: dependabot[bot]

Poem

A failed run finds a second door,
Immutable tags guard the floor.
Images wait, attestations align,
Draft releases follow the line.
Gate and recovery calmly flow.

🚥 Pre-merge checks | ✅ 7
✅ Passed checks (7 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
No Private Note Leakage ✅ Passed Changed code adds GitHub/GHCR workflow checks only; no Obsidian/bookmark/APNs/Syncthing/receipt terms or private-note logging were introduced.
Bounded Ios Background Work ✅ Passed PR changes only a Ruby safety script and a YAML workflow; no Swift/iOS background-execution code was touched, so the check is not applicable.
Bridge Contract Compatibility ✅ Passed PR changes only .github workflow/script files; no Go or Swift bridge-service types, JSON shapes, or bridge tests were modified.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title uses a concise conventional-commit style and accurately reflects the recovery-focused publication safety changes.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/helper-publication-draft-recovery

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@psimaker
psimaker marked this pull request as ready for review July 14, 2026 10:43

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/scripts/notify-publish-safety.rb:
- Around line 219-221: Update the checkout policy assertion to use dig when
reading the nested with/ref values, so missing with or ref entries yield nil and
assert_policy reports the existing policy failure instead of raising KeyError.
Preserve the immutable release_sha comparison and message.

In @.github/workflows/docker.yml:
- Line 36: Update the workflow concurrency group expression near the release-tag
selection so manually dispatched runs use a run-scoped fallback until the owner
check passes, preventing untrusted dispatches from sharing or evicting the
release-tag group; preserve the existing release-tag grouping only for
authorized owner publications.
- Around line 294-295: Split the recovery and non-recovery paths for both
publish-image (.github/workflows/docker.yml#L294-L295) and release-binaries
(.github/workflows/docker.yml#L506-L507) into separate jobs so recovery runs do
not inherit id-token: write or attestations: write. Retain only each job’s
required publish/write permissions, and place attestation and OIDC-dependent
steps in the non-recovery jobs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8d58d2de-345b-4c9c-a69d-974ecd785881

📥 Commits

Reviewing files that changed from the base of the PR and between 664a896 and 174a458.

📒 Files selected for processing (2)
  • .github/scripts/notify-publish-safety.rb
  • .github/workflows/docker.yml
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
.github/workflows/**/*.yml

⚙️ CodeRabbit configuration file

.github/workflows/**/*.yml: Review CI for correct Go test tags, Xcode/iOS simulator assumptions, secret scoping,
dependency integrity, permissions, Docker publishing safety, and unnecessary privilege escalation.

Files:

  • .github/workflows/docker.yml
🔇 Additional comments (2)
.github/workflows/docker.yml (2)

23-34: LGTM!

Also applies to: 143-164, 168-206, 208-223, 260-279, 312-316, 332-336, 352-361, 375-380, 436-458, 471-495, 522-526, 578-650, 723-790, 794-817, 860-885, 899-923, 968-973, 1008-1016, 1020-1027, 1042-1046


207-207: 🎯 Functional Correctness

No change needed here. The workflow run path field is just the workflow file path, so the exact match is fine.

			> Likely an incorrect or invalid review comment.

Comment thread .github/scripts/notify-publish-safety.rb Outdated
Comment thread .github/workflows/docker.yml Outdated
Comment thread .github/workflows/docker.yml Outdated
Keep unauthorized dispatches out of release-tag concurrency groups and make malformed checkout policy entries fail with the intended diagnostic.

Split normal image and binary attestation work from read-only recovery so recovery jobs cannot receive OIDC or attestation write permissions.

Preserve exact source, digest, draft, and public retry verification across both paths.
@psimaker

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@psimaker
psimaker merged commit 8832ef0 into main Jul 14, 2026
33 checks passed
@psimaker
psimaker deleted the agent/helper-publication-draft-recovery branch July 14, 2026 12:00
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.

1 participant