fix(release): copy before signing in publish mode too ai:gpt-5.6-sol #85

Manually merged
heiko merged 1 commit from fix/publish-outdir-signed-dsc into master 2026-08-18 11:14:50 +02:00
Owner

Third and last defect from the same cert-proxy release attempt (after #83 and #84). With #84's fix the run got through packaging and uploaded every Forgejo asset, then died on the final step:

dupload: error: Size mismatch for cert-proxy_1.20.1~0.20260818063709~a84c7d065c29.dsc on disk: 765 != 1677

Cause

finishStage already copies packaged artifacts into the output directory before staging them for upload, exactly so that debsign's in-place rewrite of the .dsc is the last write to that file. The existing comment spells the hazard out in full — and then the guard excluded the publish stage:

if r.flags.Outdir != "" && target != StagePublish {

deferring to publishLocal, which runs after stageAndSignDupload. So with --out and a single dupload destination, where staging directory and output directory are the same out/<version>/deb:

  1. debsign signs the .dsc in place (765 → 1677 bytes) and records that size and checksum in the .changes, which it then signs;
  2. publishLocal copies the assets from the temp directory, overwriting the signed .dsc with the unsigned original;
  3. dupload validates the .changes against the files on disk and refuses.

The failure lands at the very end, with everything built and the release assets already uploaded — the most expensive possible moment.

Only a single-invocation publish --out is affected. pack --out followed by publish --out copies during the pack stage, and a publish without --out never has staging and output collide, which is why this went unnoticed.

Fix

The condition now covers every target. publishLocal still runs and is simply a no-op, since copyAssetsToOutdir skips assets whose destination already equals their current path.

Tests

TestFinishStage_KeepsDebsignSignatureInOutdir guarded this for StagePack only — the branch that was excluded was never exercised, which is exactly how the regression survived. The new TestFinishStage_KeepsDebsignSignatureInOutdirOnPublish drives finishStage(StagePublish) with a fake debsign that really rewrites the .dsc, and asserts the file dupload would validate still carries the signature, and that staged and published .dsc are identical.

It uses DryRun rather than SkipUpload as the stopping point: SkipUpload returns from stageAndSignDupload before anything is staged, so it cannot reach the ordering under test. No archive, no network.

Verified to fail without the production change (Should be true on the signature prefix) and pass with it.

Verification

go build ./..., go test ./..., go vet ./... pass; golangci-lint run --new-from-rev origin/master reports 0 issues. Cut from master, independent of #83 and #84.

(authored by ai:gpt-5.6-sol)

Third and last defect from the same cert-proxy release attempt (after #83 and #84). With #84's fix the run got through packaging and uploaded every Forgejo asset, then died on the final step: ``` dupload: error: Size mismatch for cert-proxy_1.20.1~0.20260818063709~a84c7d065c29.dsc on disk: 765 != 1677 ``` ## Cause `finishStage` already copies packaged artifacts into the output directory **before** staging them for upload, exactly so that debsign's in-place rewrite of the `.dsc` is the last write to that file. The existing comment spells the hazard out in full — and then the guard excluded the publish stage: ```go if r.flags.Outdir != "" && target != StagePublish { ``` deferring to `publishLocal`, which runs *after* `stageAndSignDupload`. So with `--out` and a single dupload destination, where staging directory and output directory are the same `out/<version>/deb`: 1. debsign signs the `.dsc` in place (765 → 1677 bytes) and records that size and checksum in the `.changes`, which it then signs; 2. `publishLocal` copies the assets from the temp directory, overwriting the signed `.dsc` with the unsigned original; 3. dupload validates the `.changes` against the files on disk and refuses. The failure lands at the very end, with everything built and the release assets already uploaded — the most expensive possible moment. Only a single-invocation `publish --out` is affected. `pack --out` followed by `publish --out` copies during the pack stage, and a `publish` without `--out` never has staging and output collide, which is why this went unnoticed. ## Fix The condition now covers every target. `publishLocal` still runs and is simply a no-op, since `copyAssetsToOutdir` skips assets whose destination already equals their current path. ## Tests `TestFinishStage_KeepsDebsignSignatureInOutdir` guarded this for `StagePack` only — the branch that was excluded was never exercised, which is exactly how the regression survived. The new `TestFinishStage_KeepsDebsignSignatureInOutdirOnPublish` drives `finishStage(StagePublish)` with a fake debsign that really rewrites the `.dsc`, and asserts the file dupload would validate still carries the signature, and that staged and published `.dsc` are identical. It uses `DryRun` rather than `SkipUpload` as the stopping point: `SkipUpload` returns from `stageAndSignDupload` before anything is staged, so it cannot reach the ordering under test. No archive, no network. **Verified to fail without the production change** (`Should be true` on the signature prefix) and pass with it. ## Verification `go build ./...`, `go test ./...`, `go vet ./...` pass; `golangci-lint run --new-from-rev origin/master` reports 0 issues. Cut from `master`, independent of #83 and #84. *(authored by ai:gpt-5.6-sol)*
A single "gogogo publish --out out/" aborted in dupload:

  dupload: error: Size mismatch for cert-proxy_1.20.1~...dsc
  on disk: 765 != 1677

finishStage already copies the packaged artifacts into the output directory
before staging them, precisely so debsign's in-place rewrite of the .dsc is
the last write to that file. That copy was skipped for StagePublish and left
to publishLocal, which runs after staging: the signed .dsc in
out/<version>/deb was overwritten by the unsigned original from the temp
directory, leaving a signed .changes whose checksums described a file no
longer on disk. dupload validates before transferring, so the release failed
at the last step with everything already built and the Forgejo assets
uploaded.

The condition now covers every target. publishLocal still runs and becomes a
no-op, because copyAssetsToOutdir skips assets already at their destination.

Only a single-invocation publish with --out was affected: "pack --out"
followed by "publish --out" copies during the pack stage, and a publish
without --out never has staging and output collide.

The existing regression test covered StagePack only, which is why the
excluded branch survived. The new case drives finishStage(StagePublish) with
a fake debsign that really rewrites the .dsc, and asserts the file dupload
would validate still carries the signature. It fails without this change.
heiko manually merged commit e38690c942 into master 2026-08-18 11:14:50 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
heiko/gogogo!85
No description provided.