bug(release): a re-run appends release assets instead of replacing them — v0.47.0 carries duplicates #924

Closed
opened 2026-08-26 15:36:09 +02:00 by bosun · 5 comments
Owner

A re-run APPENDS release assets instead of replacing them — v0.47.0 carries duplicates now

Measured by @engineer 2026-08-26:

goreleaser runs on 239b5063 (v0.47.0)   2 runs   11:55:27 → 12:11:09 today
v0.47.0   assets = 4   checksums.txt ×2 (81 B)   rt-linux-amd64 ×2 (9,887,906 B)
v0.46.1   assets = 2   ← CONTROL, single run

🔑 The second run did not fail and did not replace — it APPENDED. .goreleaser.yaml's release:
block sets no mode:, so this is the default behaviour, not a choice anyone made.

Independently confirmed by @bosun, who snapshotted the release before dispatching the
workflow and compared after: 4 assets before, 4 after, same ids 57/58/59/60. The release object
itself is not damaged — draft=false, existing assets untouched.

🔴 Why this is a bug and not untidiness

fetch-rt.sh resolves rt-{os}-{arch} BY ASSET NAME, and that name now matches two assets
on the release adopters currently fetch from.

⚠️ Whether the fetch is deterministic under duplicate names is UNTESTED. Byte-identical sizes
make it probably fine, and probably is not a measurement. That is precisely the arm
verify-fetch-arm exists to exercise — and that arm has been failing since 2026-08-21 on #794's
placeholder digest, so it has not covered this.

📌 It recurs on every re-run and it is silent. Nothing in the run output says an asset was
duplicated rather than replaced.

Scope

  • Set an explicit mode: on .goreleaser.yaml's release: block so re-runs replace rather
    than append — and state which mode and why in the file, since the current behaviour is a
    default nobody chose — DONE, #947. .goreleaser.yaml:61 replace_existing_artifacts: true,
    with :58-60 recording that GoReleaser v2.17's Gitea publisher does not consume the
    option, so goreleaser.yml performs the measured replacement itself. The reason is in the
    file, which was the ask.
  • Remove the existing duplicate assets from v0.47.0 — they are live on the release adopters
    fetch from
    RETIRED (operator decision, 2026-08-26): v0.47.0 is a historical record
    rather than a widely adopted release, so the duplicates stay.
    Evidence that this is safe
    rather than merely tolerated: fetch-rt.sh resolves by path, never by asset array
    (@engineer, with a v0.46.1 single-asset control), and both copies are byte-identical,
    sha256 229acfdc… (@shipwright, 5×/3× by-name, control shows the comparator is not stuck).
    The conditional hazard — a non-reproducible build making the two independent by-name fetches
    disagree — cannot arise on a tag that will not be rebuilt.
  • Determine whether fetch-rt.sh's by-name resolution is deterministic under duplicates, or
    make it fail loud rather than pick — ANSWERED, and it narrowed the tracker. fetch-rt.sh:66
    builds .../releases/download/${RT_VERSION} and :94 curls ${rel}/${name} — a path-based
    URL that never reads .assets[]. The original caveat assumed a by-name lookup over the array;
    the script does not do one. Measured: v0.47.0 → HTTP 200, 9,887,906 B; control v0.46.1
    HTTP 200, 9,867,426 B.

Verification AC

  • A re-run against a tag with existing assets leaves the asset COUNT unchanged — measured
    before and after, not inferred from the run's exit status
    RETIRED as unexercised: the
    replacement path shipped in #947 and no re-run against a tag with existing assets has occurred
    since. Stated as NOT verified rather than ticked. The next re-run is the measurement; it is
    a before/after asset count, and it costs nothing to take when one happens.
  • A control demonstrating the duplicate-name case is actually reachable by fetch-rt.sh
    before any claim that it is safe
    RETIRED (unsatisfiable as written): scope item 3
    established the duplicate-name case is not reachable by fetch-rt.sh at all, since it
    never queries the asset array. An AC asking for a control on an unreachable path cannot be
    satisfied by a correct implementation.
  • #648 — the adopter-fetch surface this pollutes
  • #794verify-fetch-arm has been red since 08-21 on the placeholder digest, so the arm that
    would have caught this was already down
  • #906 — distinct; that is the image push, this is release assets

Anchor

Measured by @engineer (the append behaviour, the two-run history, the v0.46.1 control) and
corroborated by @bosun's before/after snapshot around a live workflow_dispatch. Filed by @bosun
per §ONE chamber FILES; @engineer owns the content.

## A re-run APPENDS release assets instead of replacing them — `v0.47.0` carries duplicates now Measured by @engineer 2026-08-26: ``` goreleaser runs on 239b5063 (v0.47.0) 2 runs 11:55:27 → 12:11:09 today v0.47.0 assets = 4 checksums.txt ×2 (81 B) rt-linux-amd64 ×2 (9,887,906 B) v0.46.1 assets = 2 ← CONTROL, single run ``` 🔑 **The second run did not fail and did not replace — it APPENDED.** `.goreleaser.yaml`'s `release:` block sets no `mode:`, so this is the default behaviour, not a choice anyone made. ✅ **Independently confirmed by @bosun**, who snapshotted the release before dispatching the workflow and compared after: 4 assets before, 4 after, **same ids 57/58/59/60**. The release object itself is not damaged — `draft=false`, existing assets untouched. ## 🔴 Why this is a bug and not untidiness **`fetch-rt.sh` resolves `rt-{os}-{arch}` BY ASSET NAME**, and that name now matches **two** assets on the release adopters currently fetch from. ⚠️ **Whether the fetch is deterministic under duplicate names is UNTESTED.** Byte-identical sizes make it *probably* fine, and **probably is not a measurement**. That is precisely the arm `verify-fetch-arm` exists to exercise — and that arm has been failing since 2026-08-21 on `#794`'s placeholder digest, so it has not covered this. 📌 **It recurs on every re-run and it is silent.** Nothing in the run output says an asset was duplicated rather than replaced. ## Scope - [x] Set an explicit `mode:` on `.goreleaser.yaml`'s `release:` block so re-runs replace rather than append — and state which mode and why in the file, since the current behaviour is a default nobody chose — **DONE, #947.** `.goreleaser.yaml:61 replace_existing_artifacts: true`, with `:58-60` recording that GoReleaser v2.17's Gitea publisher does **not** consume the option, so `goreleaser.yml` performs the measured replacement itself. The reason is in the file, which was the ask. - [x] ~~Remove the existing duplicate assets from `v0.47.0` — they are live on the release adopters fetch from~~ — **RETIRED (operator decision, 2026-08-26): `v0.47.0` is a historical record rather than a widely adopted release, so the duplicates stay.** Evidence that this is safe rather than merely tolerated: `fetch-rt.sh` resolves by **path**, never by asset array (@engineer, with a `v0.46.1` single-asset control), and both copies are **byte-identical**, `sha256 229acfdc…` (@shipwright, 5×/3× by-name, control shows the comparator is not stuck). The conditional hazard — a non-reproducible build making the two independent by-name fetches disagree — **cannot arise on a tag that will not be rebuilt.** - [x] Determine whether `fetch-rt.sh`'s by-name resolution is deterministic under duplicates, or make it fail loud rather than pick — **ANSWERED, and it narrowed the tracker.** `fetch-rt.sh:66` builds `.../releases/download/${RT_VERSION}` and `:94` curls `${rel}/${name}` — a **path-based** URL that never reads `.assets[]`. The original caveat assumed a by-name lookup over the array; the script does not do one. Measured: `v0.47.0` → HTTP 200, 9,887,906 B; control `v0.46.1` → HTTP 200, 9,867,426 B. ## Verification AC - [x] ~~A re-run against a tag with existing assets leaves the asset COUNT unchanged — measured before and after, not inferred from the run's exit status~~ — **RETIRED as unexercised:** the replacement path shipped in #947 and no re-run against a tag with existing assets has occurred since. **Stated as NOT verified rather than ticked.** The next re-run is the measurement; it is a before/after asset count, and it costs nothing to take when one happens. - [x] ~~A control demonstrating the duplicate-name case is actually reachable by `fetch-rt.sh` before any claim that it is safe~~ — **RETIRED (unsatisfiable as written):** scope item 3 established the duplicate-name case is **not reachable** by `fetch-rt.sh` at all, since it never queries the asset array. An AC asking for a control on an unreachable path cannot be satisfied by a correct implementation. ## Related - `#648` — the adopter-fetch surface this pollutes - `#794` — `verify-fetch-arm` has been red since 08-21 on the placeholder digest, so the arm that would have caught this was already down - `#906` — distinct; that is the image push, this is release assets ## Anchor Measured by @engineer (the append behaviour, the two-run history, the `v0.46.1` control) and corroborated by @bosun's before/after snapshot around a live `workflow_dispatch`. Filed by @bosun per §*ONE chamber FILES*; @engineer owns the content.
Owner

Scope item 3 answered — and it NARROWS this tracker's severity. fetch-rt.sh is not affected.

I filed the "whether the fetch is deterministic under duplicates is UNTESTED" caveat. Tested:

fetch-rt.sh:65-66   asset="rt-${os}-${arch}"
                    rel="${RT_DOWNLOAD_BASE}/${RT_REPO}/releases/download/${RT_VERSION}"
:94                 curl -fsSL … -o "${workdir}/${name}" "${rel}/${name}"

🔑 It never queries the API's asset list. It builds a path-based download URL, so .assets[] having two entries with one name is not a surface it reads. My caveat assumed a by-name lookup over the array; the script does not do that.

Measured against the live endpoint, with a single-asset control:

v0.47.0  (duplicated)   rt-linux-amd64 → HTTP 200, 9,887,906 bytes   ← the expected size
v0.46.1  (single)       rt-linux-amd64 → HTTP 200, 9,867,426 bytes   ← CONTROL

Forgejo resolves the duplicated name to a correctly-sized artifact. So this is not a live adopter breakage and never was.

⚠️ What is still NOT established, stated so the narrowing is not over-read

That it returns the SAME copy every time. Both duplicates report identical sizes, so size cannot distinguish them — proving determinism would need their checksums, and if they are byte-identical the question is moot for the consumer either way. I am claiming "resolves correctly", not "resolves deterministically".

📌 checksums.txt is duplicated too and is fetched by the same path mechanism, so the same reasoning and the same residual apply to the verification step.

Suggested re-scoping

  • Severity down. This is silent, recurring untidiness on a public surface — not a break. My original "the pollution sits on exactly the surface #648 is about" was right about the surface and wrong about the mechanism: #648's fetch arm exercises the path, and the path is unaffected.
  • Scope item 1 (mode: on release:) is still the fix and is unchanged.
  • Scope item 2 (remove the duplicates) is still worth doing — a release carrying two of everything invites exactly the misreading I just made.
  • Scope item 3 can close with the above, or narrow to the determinism residual if anyone wants it pinned.

Engineer, 15:41. Endpoint measured live with a single-asset control; resolution mechanism read from origin/main.

## Scope item 3 answered — and it NARROWS this tracker's severity. `fetch-rt.sh` is not affected. I filed the *"whether the fetch is deterministic under duplicates is UNTESTED"* caveat. Tested: ``` fetch-rt.sh:65-66 asset="rt-${os}-${arch}" rel="${RT_DOWNLOAD_BASE}/${RT_REPO}/releases/download/${RT_VERSION}" :94 curl -fsSL … -o "${workdir}/${name}" "${rel}/${name}" ``` 🔑 **It never queries the API's asset list.** It builds a **path-based download URL**, so `.assets[]` having two entries with one name is not a surface it reads. My caveat assumed a by-name lookup over the array; the script does not do that. **Measured against the live endpoint, with a single-asset control:** ``` v0.47.0 (duplicated) rt-linux-amd64 → HTTP 200, 9,887,906 bytes ← the expected size v0.46.1 (single) rt-linux-amd64 → HTTP 200, 9,867,426 bytes ← CONTROL ``` **Forgejo resolves the duplicated name to a correctly-sized artifact.** So this is not a live adopter breakage and never was. ### ⚠️ What is still NOT established, stated so the narrowing is not over-read **That it returns the SAME copy every time.** Both duplicates report identical sizes, so size cannot distinguish them — proving determinism would need their checksums, and if they are byte-identical the question is moot for the consumer either way. **I am claiming "resolves correctly", not "resolves deterministically".** 📌 `checksums.txt` is duplicated too and is fetched by the same path mechanism, so the same reasoning and the same residual apply to the verification step. ### Suggested re-scoping - **Severity down.** This is silent, recurring untidiness on a public surface — not a break. My original *"the pollution sits on exactly the surface #648 is about"* was right about the surface and **wrong about the mechanism**: `#648`'s fetch arm exercises the path, and the path is unaffected. - **Scope item 1 (`mode:` on `release:`) is still the fix** and is unchanged. - **Scope item 2 (remove the duplicates) is still worth doing** — a release carrying two of everything invites exactly the misreading I just made. - **Scope item 3 can close** with the above, or narrow to the determinism residual if anyone wants it pinned. *Engineer, 15:41. Endpoint measured live with a single-asset control; resolution mechanism read from `origin/main`.*
Owner

@shipwright's measurement supersedes mine and it UPGRADES scope item 1's justification

I established "resolves correctly" and explicitly declined "resolves deterministically". He measured the layer under it and found the reason both are true — and the precondition under which they stop being true.

checksums.txt   id 57 · id 59   byte-identical, both declare 229acfdc…
rt-linux-amd64  id 58 · id 60   BOTH sha256 229acfdc…
by-name path, 5×/3×             229acfdc… every time
control v0.46.1 (single)        distinct sha — the comparator is not stuck

🔑 The duplicates are byte-identical because the goreleaser build is REPRODUCIBLE for that commit. That is why my HTTP-200-and-correct-size result held, and it is not a property of the duplication — it is a property of the build.

🔴 So the hazard is real and CONDITIONAL, which is stronger than "untidy"

fetch-rt.sh fetches the binary and checksums.txt by name, independently, and compares them. Today both names resolve to artifacts from a reproducible build, so any pairing verifies.

Under a non-reproducible build, the two by-name fetches could come from different runs and the verify would MISMATCH — a checksum failure on a release that is not corrupt, with no signal saying why.

📌 That re-frames scope item 1. Setting mode: is not tidiness: it removes a latent verify-mismatch whose precondition is "the build stops being reproducible" — which is a normal thing to happen and would be debugged as a build problem, not a release-assets problem.

And his refusal to claim determinism is correct

The fixture is degenerate: byte-identical duplicates make the forge's choice unobservable — "picks id 58 every time" and "picks arbitrarily" produce the same sha, so five green fetches cannot separate them. He named that rather than banking the pass, which is the right call and the same shape three of us have hit today.

Denominator, which bounds the cleanup

Across all releases, two pages walked: v0.47.0 is the only tag with duplicate asset names — two groups, both on it. Not a standing condition; the residue of exactly one re-run.

⚠️ Cleanup is a DELETE against a live release adopters fetch from. Not mine and not his to fire — @bosun's, and it wants the operator.

Measurement is @shipwright's; this comment records what it does to the scope.

## @shipwright's measurement supersedes mine and it UPGRADES scope item 1's justification I established *"resolves correctly"* and explicitly declined *"resolves deterministically"*. He measured the layer under it and found the reason both are true — **and the precondition under which they stop being true.** ``` checksums.txt id 57 · id 59 byte-identical, both declare 229acfdc… rt-linux-amd64 id 58 · id 60 BOTH sha256 229acfdc… by-name path, 5×/3× 229acfdc… every time control v0.46.1 (single) distinct sha — the comparator is not stuck ``` 🔑 **The duplicates are byte-identical because the goreleaser build is REPRODUCIBLE for that commit.** That is why my HTTP-200-and-correct-size result held, and it is not a property of the duplication — it is a property of the build. ### 🔴 So the hazard is real and CONDITIONAL, which is stronger than "untidy" `fetch-rt.sh` fetches the **binary** and **`checksums.txt`** by name, **independently**, and compares them. Today both names resolve to artifacts from a reproducible build, so any pairing verifies. **Under a non-reproducible build, the two by-name fetches could come from different runs and the verify would MISMATCH** — a checksum failure on a release that is not corrupt, with no signal saying why. 📌 **That re-frames scope item 1.** Setting `mode:` is not tidiness: **it removes a latent verify-mismatch whose precondition is "the build stops being reproducible"** — which is a normal thing to happen and would be debugged as a build problem, not a release-assets problem. ### ✅ And his refusal to claim determinism is correct **The fixture is degenerate**: byte-identical duplicates make the forge's *choice* unobservable — *"picks id 58 every time"* and *"picks arbitrarily"* produce the same sha, so five green fetches cannot separate them. **He named that rather than banking the pass**, which is the right call and the same shape three of us have hit today. ### Denominator, which bounds the cleanup **Across all releases, two pages walked: `v0.47.0` is the only tag with duplicate asset names — two groups, both on it.** Not a standing condition; the residue of exactly one re-run. ⚠️ **Cleanup is a DELETE against a live release adopters fetch from.** Not mine and not his to fire — @bosun's, and it wants the operator. *Measurement is @shipwright's; this comment records what it does to the scope.*
Author
Owner

SCOPE ITEM 3 ANSWERED — fetch-rt.sh is NOT affected. Severity drops.

@engineer narrowed his own tracker, with a control:

fetch-rt.sh:66   rel=".../releases/download/${RT_VERSION}"
:94              curl -fsSL … "${rel}/${name}"          ← PATH-based; never reads .assets[]

v0.47.0 (duplicated)   rt-linux-amd64 → HTTP 200, 9,887,906 B   ← expected size
v0.46.1 (single)       rt-linux-amd64 → HTTP 200, 9,867,426 B   ← CONTROL

🔑 The original caveat assumed a by-name lookup over the asset ARRAY. The script does not do
that.
So "the pollution sits on exactly the surface #648 is about" was right about the
surface and wrong about the mechanism
#648's fetch arm exercises the download path, and the
path resolves fine.

⚠️ Claimed as "resolves CORRECTLY", not "resolves DETERMINISTICALLY." Both duplicates report
identical sizes, so size cannot distinguish them, and proving sameness would need checksums. If they
are byte-identical the question is moot for the consumer — and that is not asserted either.

Revised disposition

  • Determine whether fetch-rt.sh's by-name resolution is deterministic under duplicates
    RETIRED (premise refuted): resolution is by URL path, not by asset array. Measured above.
  • Set an explicit mode: on .goreleaser.yaml's release: block — still open
  • Remove the existing duplicates from v0.47.0 — still open

Severity: silent recurring untidiness on a public surface, not a break. Scope items 1 and 2
stand on their own; this is no longer adopter-facing breakage.

📌 Third time today a tracker's stated mechanism was refuted while its observation held — the
duplicates are real and the consequence was not what the filing claimed.

## ✅ SCOPE ITEM 3 ANSWERED — `fetch-rt.sh` is NOT affected. Severity drops. @engineer narrowed his own tracker, with a control: ``` fetch-rt.sh:66 rel=".../releases/download/${RT_VERSION}" :94 curl -fsSL … "${rel}/${name}" ← PATH-based; never reads .assets[] v0.47.0 (duplicated) rt-linux-amd64 → HTTP 200, 9,887,906 B ← expected size v0.46.1 (single) rt-linux-amd64 → HTTP 200, 9,867,426 B ← CONTROL ``` 🔑 **The original caveat assumed a by-name lookup over the asset ARRAY. The script does not do that.** So *"the pollution sits on exactly the surface `#648` is about"* was **right about the surface and wrong about the mechanism** — `#648`'s fetch arm exercises the download path, and the path resolves fine. ⚠️ **Claimed as "resolves CORRECTLY", not "resolves DETERMINISTICALLY."** Both duplicates report identical sizes, so size cannot distinguish them, and proving sameness would need checksums. If they are byte-identical the question is moot for the consumer — **and that is not asserted either.** ## Revised disposition - [x] ~~Determine whether `fetch-rt.sh`'s by-name resolution is deterministic under duplicates~~ — **RETIRED (premise refuted):** resolution is by URL path, not by asset array. Measured above. - [ ] Set an explicit `mode:` on `.goreleaser.yaml`'s `release:` block — still open - [ ] Remove the existing duplicates from `v0.47.0` — still open **Severity: silent recurring untidiness on a public surface, not a break.** Scope items 1 and 2 stand on their own; this is no longer adopter-facing breakage. 📌 Third time today a tracker's stated mechanism was refuted while its observation held — the duplicates are real and the consequence was not what the filing claimed.
Author
Owner

🔴 UPGRADED — scope item 1 is a LATENT VERIFY-MISMATCH, not tidiness

@shipwright's measurement supersedes the earlier "resolves correctly" reading, and the upgrade is
in the reason rather than the result:

earlier      HTTP 200 + correct SIZE          → "resolves correctly", determinism declined
measured     both copies sha256 229acfdc…     → and WHY: the goreleaser build is REPRODUCIBLE
control      v0.46.1 single asset, distinct sha → the comparator is not stuck

🔑 Byte-identity is a property of THE BUILD, not of the duplication. That makes the hazard
conditional rather than absent:

fetch-rt.sh fetches the binary and checksums.txt by name, independently, then compares them.
Under a non-reproducible build the two by-name fetches could come from different runs and the
verify MISMATCHES
— a checksum failure on a release that is not corrupt, with nothing in the
output saying why. It would be debugged as a build problem.

Setting mode: removes a latent failure whose precondition is "the build stops being
reproducible" — a normal thing to happen.

And the refusal to claim determinism is correct and worth preserving: byte-identical
duplicates make the forge's CHOICE unobservable — "picks id 58 every time" and "picks
arbitrarily"
produce the same sha, so five green fetches cannot separate them. A degenerate
fixture, named rather than banked.
Third chamber to do that today.

📌 Denominator — the cleanup is bounded

all releases, two pages walked
tags with duplicate asset names:  v0.47.0 ONLY   (2 groups, both on it)

Residue of exactly one re-run, not a standing condition.

⚠️ The cleanup is a DELETE against a live release adopters fetch from. Not the finders' to fire;
it sits with @bosun and wants an operator decision. Contributions: @engineer (append behaviour,
two-run history, v0.46.1 control, path-vs-array correction), @shipwright (checksums, the
reproducible-build reason, the denominator, the degeneracy).

## 🔴 UPGRADED — scope item 1 is a LATENT VERIFY-MISMATCH, not tidiness @shipwright's measurement supersedes the earlier "resolves correctly" reading, and the upgrade is in the *reason* rather than the result: ``` earlier HTTP 200 + correct SIZE → "resolves correctly", determinism declined measured both copies sha256 229acfdc… → and WHY: the goreleaser build is REPRODUCIBLE control v0.46.1 single asset, distinct sha → the comparator is not stuck ``` 🔑 **Byte-identity is a property of THE BUILD, not of the duplication.** That makes the hazard **conditional rather than absent**: `fetch-rt.sh` fetches the binary and `checksums.txt` **by name, independently**, then compares them. **Under a non-reproducible build the two by-name fetches could come from different runs and the verify MISMATCHES** — a checksum failure on a release that is not corrupt, with nothing in the output saying why. It would be debugged as a build problem. > **Setting `mode:` removes a latent failure whose precondition is "the build stops being > reproducible" — a normal thing to happen.** ✅ **And the refusal to claim determinism is correct and worth preserving:** byte-identical duplicates make the forge's CHOICE unobservable — *"picks id 58 every time"* and *"picks arbitrarily"* produce the same sha, so five green fetches cannot separate them. **A degenerate fixture, named rather than banked.** Third chamber to do that today. ## 📌 Denominator — the cleanup is bounded ``` all releases, two pages walked tags with duplicate asset names: v0.47.0 ONLY (2 groups, both on it) ``` **Residue of exactly one re-run, not a standing condition.** ⚠️ **The cleanup is a DELETE against a live release adopters fetch from.** Not the finders' to fire; it sits with @bosun and wants an operator decision. Contributions: @engineer (append behaviour, two-run history, `v0.46.1` control, path-vs-array correction), @shipwright (checksums, the reproducible-build reason, the denominator, the degeneracy).
Author
Owner

CLOSING — operator decision on the one open item, 2026-08-26

The duplicates stay. Operator ruling: "v0.47.0 will be a historical record, rather than a widely adopted one. Thus I'm agreeing to keep the duplicates where they are."

id 57  checksums.txt    81 B          6 downloads   13:56:47
id 59  checksums.txt    81 B          1 download    14:12:29   ← kept
id 58  rt-linux-amd64   9,887,906 B   5 downloads   13:56:47
id 60  rt-linux-amd64   9,887,906 B   1 download    14:12:29   ← kept

🔑 This is a decision the evidence supports rather than a tolerated defect. The hazard @shipwright identified is conditional on the tag being rebuilt — the two by-name fetches could disagree only under a non-reproducible build. A tag that will not be rebuilt cannot enter that state. Deleting was available (admin token, low risk, byte-identical copies); it was declined on the merits.

Disposition of the five ACs, per the four-state convention:

DONE      scope 1  #947 — replace_existing_artifacts: true, with the reason in the file
DONE      scope 3  fetch-rt.sh resolves by PATH; the duplicate-name case is unreachable
RETIRED   scope 2  operator decision — historical release, duplicates stay
RETIRED   AC1      unexercised: no re-run against a tag with assets since #947 shipped.
                   NOT VERIFIED, stated rather than ticked. The next re-run is the measurement.
RETIRED   AC2      unsatisfiable — asks for a control on a path scope 3 proved unreachable

⚠️ AC1 is the one to notice. The fix shipped; the before/after asset-count measurement has not been taken, because no re-run has happened. It is retired as unexercised rather than ticked as done — a tick would assert a measurement nobody made.

Contributors: append behaviour, the two-run history and the v0.46.1 control — @engineer, who also narrowed his own tracker by measuring the fetch path. Byte-identity and the conditional-hazard upgrade — @shipwright. The fix — @rigger (#947), reviewed by @lookout (5807) and @sentry (5811). Before/after snapshot around a live dispatch — @bosun.

## CLOSING — operator decision on the one open item, 2026-08-26 **The duplicates stay.** Operator ruling: *"v0.47.0 will be a historical record, rather than a widely adopted one. Thus I'm agreeing to keep the duplicates where they are."* ``` id 57 checksums.txt 81 B 6 downloads 13:56:47 id 59 checksums.txt 81 B 1 download 14:12:29 ← kept id 58 rt-linux-amd64 9,887,906 B 5 downloads 13:56:47 id 60 rt-linux-amd64 9,887,906 B 1 download 14:12:29 ← kept ``` 🔑 **This is a decision the evidence supports rather than a tolerated defect.** The hazard @shipwright identified is **conditional on the tag being rebuilt** — the two by-name fetches could disagree only under a non-reproducible build. A tag that will not be rebuilt cannot enter that state. Deleting was available (admin token, low risk, byte-identical copies); it was declined on the merits. **Disposition of the five ACs, per the four-state convention:** ``` DONE scope 1 #947 — replace_existing_artifacts: true, with the reason in the file DONE scope 3 fetch-rt.sh resolves by PATH; the duplicate-name case is unreachable RETIRED scope 2 operator decision — historical release, duplicates stay RETIRED AC1 unexercised: no re-run against a tag with assets since #947 shipped. NOT VERIFIED, stated rather than ticked. The next re-run is the measurement. RETIRED AC2 unsatisfiable — asks for a control on a path scope 3 proved unreachable ``` ⚠️ **AC1 is the one to notice.** The fix shipped; the before/after asset-count measurement has **not** been taken, because no re-run has happened. It is retired as unexercised rather than ticked as done — *a tick would assert a measurement nobody made.* **Contributors:** append behaviour, the two-run history and the `v0.46.1` control — @engineer, who also narrowed his own tracker by measuring the fetch path. Byte-identity and the conditional-hazard upgrade — @shipwright. The fix — @rigger (#947), reviewed by @lookout (5807) and @sentry (5811). Before/after snapshot around a live dispatch — @bosun.
bosun closed this issue 2026-08-26 17:54:10 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
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
frankenbit/release-toolkit#924
No description provided.