Skip to content

fix(release): let detect resume correlate-audit from an attached successful attempt - #867

Merged
blove merged 1 commit into
mainfrom
blove/epic-leakey-4175f6
Sep 30, 2026
Merged

blove merged 1 commit into
mainfrom
blove/epic-leakey-4175f6

Conversation

@blove

@blove blove commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

Problem

During the v0.13.0 release (2026-09-28/29), correlate-audit's recordAuditAttempt uploaded audit-attempt-<run>-<attempt>.json while the marker stayed AUDIT_DISPATCHED. That's correct: a successful attempt doesn't move the marker. Then the job failed on a transient "GitHub release-asset-download bytes are not exact" before verifyAuditSuccess could canonicalize the attempt and advance the marker to AUDIT_VERIFIED.

Both writers are idempotent over that state, but detect refused it. validateAuditAssetPhase (scripts/release/observe.mjs) threw RELEASE_AUDIT_DISPATCH_EVIDENCE_PREMATURE for any current-dispatch attempt under AUDIT_DISPATCHED. The release only finished after someone deleted the attempt asset by hand and dispatched reconcile.

Fix

Under AUDIT_DISPATCHED, detect now accepts a current-dispatch attempt only when all of these hold:

  • the parsed result has conclusion: "success"
  • result.workflowRunId equals marker.audit.workflowRunId
  • result.runAttempt equals the audit run's latest attempt
  • the run is completed / success
  • the filename is exactly audit-attempt-<workflowRunId>-<runAttempt>.json

The existing checks still run before this one: candidate version, commit, manifest digest, listed-digest bytes and canonical bytes. The observation reports audit.status: "dispatched", and the planner proposes complete-release-audit, so correlate-audit reruns and finishes through the idempotent writers. The writer-side resume is already covered by audit.test.mjs ("audit writers resume runner loss after attempt, canonical, and marker mutations").

Still fail-closed (each has a regression test)

Variant Code
failure-conclusion attempt under AUDIT_DISPATCHED RELEASE_AUDIT_DISPATCH_EVIDENCE_PREMATURE
success attempt, run still in progress RELEASE_AUDIT_DISPATCH_EVIDENCE_PREMATURE
success attempt superseded by a later run attempt RELEASE_AUDIT_DISPATCH_EVIDENCE_PREMATURE
success attempt, run concluded unsuccessfully RELEASE_AUDIT_DISPATCH_EVIDENCE_PREMATURE
different manifest digest / candidate commit RELEASE_AUDIT_RECEIPT_IDENTITY_MISMATCH
filename names another attempt RELEASE_AUDIT_ATTEMPT_IDENTITY_MISMATCH
bytes differ from listed digest RELEASE_AUDIT_ASSET_DIGEST_MISMATCH
noncanonical bytes RELEASE_AUDIT_ASSET_NONCANONICAL
audit-result.json without AUDIT_VERIFIED RELEASE_AUDIT_CANONICAL_PREMATURE
duplicate current-dispatch attempts RELEASE_AUDIT_CURRENT_ATTEMPT_AMBIGUOUS

The new resumable-state test fails against the pre-fix observe.mjs and passes with the fix.

Other paths checked, no change needed

  • Evidence / observation schema / planner: they already allow a current attempt under AUDIT_DISPATCHED. analyzeTerminalAssets only binds the marker-named attempt, releaseEvidenceAssetIsAllowed permits it, and the planner maps AUDIT_DISPATCHED to complete-release-audit.
  • Recovery (scripts/release/recovery/observe.mjs): it uses its own recovery-v2-audit-* bookkeeping. Uploads are idempotent (same name, same bytes returns the existing asset), and AUDIT_PENDING already tolerates a result without its escrow, so it doesn't have this assumption.
  • Postpublication executor: it only authorizes the executor and consumes detect's plan. It does no asset-phase validation, so it follows from the fix above.

Known remaining gap (deliberately out of scope)

verifyAuditSuccess uploads audit-result.json before the marker CAS. If a runner is lost between those two steps, you get audit-result.json + AUDIT_DISPATCHED. The writers resume from that, but detect still blocks with RELEASE_AUDIT_CANONICAL_PREMATURE, as requested. The same kind of relaxation could cover it later: canonical bytes identical to the exact successful current attempt.

Kept separate from the pending duplicate tag dispatch / zero-job escrow task.

Pins

The scripts/release/observe.mjs pin is regenerated in release-script-hashes.json, and STARTING_SCRIPT_PIN_SHA256 is updated in workflow-contracts.test.mjs.

Verification

  • pnpm test:release-integrity: 33/33 pass
  • node --test scripts/release/test/workflow-contracts.test.mjs: 166/166 pass
  • pnpm test:release-controller: 3950/3952 pass. Both failures are the same recovery-rehearsal.test.mjs "effect 9 after" case: "Recovery observation blocked: exact value read unavailable" after 23s in a 17-minute contended run. That suite loads only recovery/observe.mjs, never the changed file. Rerun on its own, recovery-rehearsal.test.mjs passed 70/70.

No changeset: the change touches only scripts/release, no publishable package.

🤖 Generated with Claude Code

…essful attempt

correlate-audit attaches audit-attempt-<run>-<attempt>.json while the
marker stays AUDIT_DISPATCHED, then verifyAuditSuccess canonicalizes it
and advances to AUDIT_VERIFIED. Both steps are idempotent, but when the
job stopped between them (v0.13.0: a transient release-asset-download
byte mismatch) every later detect threw
RELEASE_AUDIT_DISPATCH_EVIDENCE_PREMATURE and the release needed the
attempt asset deleted by hand.

validateAuditAssetPhase now accepts a current-dispatch attempt under
AUDIT_DISPATCHED only when it is a successful result exactly correlated
to the recorded dispatch: same workflowRunId, the run's latest attempt,
the run completed with success, and the canonical filename. Candidate,
manifest digest, and byte checks run before it as before. The planner
then proposes complete-release-audit.

Everything else stays fail-closed: a failure attempt under
AUDIT_DISPATCHED, an in-progress, unsuccessful, or superseded run,
identity or byte drift, audit-result.json without AUDIT_VERIFIED, and
duplicate current attempts.

Regenerates the observe.mjs content pin and the pin-file digest snapshot.

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

vercel Bot commented Sep 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
b4-run Ignored Ignored Sep 29, 2026 7:02pm UTC

Request Review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Automated approval: this PR received an intelligent (AI) code review. See the review comments on this PR.

@blove
blove merged commit 6d0a2e8 into main Sep 30, 2026
30 of 31 checks passed
@blove
blove deleted the blove/epic-leakey-4175f6 branch September 30, 2026 16:50

This branch was previously deployed

1 inactive deployment
vercel-preview — 2fc56190 Deployed Sep 29, 2026 by blove via vercel-native #2459
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