Skip to content

ci(installer-contract): check asset existence on disk, not in YAML - #133

Merged
Jaro-c merged 2 commits into
mainfrom
ci/installer-contract-verify-substance
Aug 17, 2026
Merged

ci(installer-contract): check asset existence on disk, not in YAML#133
Jaro-c merged 2 commits into
mainfrom
ci/installer-contract-verify-substance

Conversation

@Jaro-c

@Jaro-c Jaro-c commented Aug 17, 2026

Copy link
Copy Markdown
Member

The problem

The SHA256SUMS clause I added in v1.14.1 cannot pass.

It looks for the manifest among the asset: keys parsed out of release.yml. Those keys are the build matrix. SHA256SUMS is computed from the matrix outputs in a later step and passed to gh release create as an argument, so it can never be one of them.

That makes the check inverted: failing it is the normal state of a correct release workflow, and the only way to pass is to declare a matrix entry that produces nothing — the exact defect it existed to catch.

Measured on 2026-08-17:

  • No repository in the org declares asset: SHA256SUMS.
  • podup is the gate's only consumer, and is still pinned at v1.13.0, which predates the clause.
  • So it has never run green anywhere. Its first contact with a real repository — the pin bump in podup#1425 — failed a workflow that does produce and sign the manifest.

Why not just fix the regex

Extending it to the gh release create arguments still reads a file for evidence that a step ran. A workflow file is a statement of intent; it is not proof of an artifact.

What I did instead

Existence is only knowable at release time, so I check it there. scripts/verify-release-assets.py stats the staged files after they are built, signed and checksummed, and before gh release create makes the release immutable.

What it requires is derived from the installer, by reading the ${BASE_URL}/... fetches out of install.sh — not hardcoded. Hardcoding the manifest name would have repeated the original mistake one layer down: the old clause never looked at SHA256SUMS.sig, which install.sh also downloads and without which it aborts, so a release missing only the signature would have passed a gate whose stated purpose was making downloads verifiable.

The gate keeps its cross-reference check, which is sound — comparing two declarations is what reading YAML is good for — and its error message now says what it did and did not look at.

Verified

Against podup's real install.sh, install.ps1 and release.yml:

case result
gate, before this change fails podup (the reported false positive)
gate, after OK: all 4 installer-requestable assets are published / all 2 install.ps1-requestable
script, complete staging dir OK: all 8 files the install scripts fetch are staged
script, SHA256SUMS.sig removed fails — the case the old clause could not see
script, SHA256SUMS removed fails, naming both missing files

docs/reusables regenerated with scripts/render-reusable-docs.py.

Follow-up

The consumer side is a separate PR in podup, which wires the script into release.yml between "Generate checksums" and "Create GitHub Release". I will not tag a release here until that PR proves this green.

The `SHA256SUMS` clause I added in v1.14.1 cannot pass. It looks for the
manifest among the `asset:` keys parsed out of release.yml, but those keys
are the build matrix and the manifest is computed *from* the matrix outputs
in a later step, so it can never be one of them. Failing it was the normal
state of a correct release workflow, and the only way to pass was to declare
a matrix entry that produced nothing — the exact defect the check existed to
catch. Measured: no repository declares that asset, podup is the gate's only
consumer and is still pinned to a tag from before the clause, so it had never
run green anywhere. Its first contact with a real repository, the pin bump in
podup#1425, failed a workflow that does produce and sign the manifest.

Extending the regex to the `gh release create` arguments would not fix it,
because that is still reading a file for evidence that a step ran. Existence
is only knowable at release time, so I check it there instead: the new
scripts/verify-release-assets.py stats the staged files after they are built,
signed and checksummed, and before `gh release create` makes the release
immutable.

What it requires is derived from the installer rather than hardcoded, by
reading the `${BASE_URL}/...` fetches out of install.sh. Hardcoding the
manifest name would have repeated the original mistake one layer down: the
old clause never looked at SHA256SUMS.sig, which install.sh also downloads
and without which it aborts, so a release missing only the signature would
have passed a gate whose stated purpose was making downloads verifiable.

The gate keeps the cross-reference check, which is sound — comparing two
declarations is what reading YAML is good for — and its error message now
says what it did and did not look at.

Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
…ld not see

Mutation-checked: replacing the on-disk lookup with an empty missing-set
turns four of the nine red, including the missing-SHA256SUMS.sig case that
the clause this script replaces had no way to detect.

Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
@Jaro-c
Jaro-c merged commit 118244b into main Aug 17, 2026
9 checks passed
@Jaro-c
Jaro-c deleted the ci/installer-contract-verify-substance branch August 17, 2026 16:11
Jaro-c added a commit to Glyndor/podup that referenced this pull request Aug 17, 2026
Supersedes #1425.

## What this unblocks

`#1425` bumps `installer-contract` from v1.13.0 to v1.14.1 and fails.
The failure is not this repository's: v1.14.1 added a check that looks
for `SHA256SUMS` among the `asset:` keys parsed out of `release.yml`.
Those keys are the build matrix, and the manifest is computed *from* the
matrix outputs in a later step — so it can never be one of them. Failing
it is the normal state of a correct release workflow.

Glyndor/.github#133 removed it and shipped v1.16.0. This bump is the
first run of the amended gate against a real repository, so it is the
proof the fix works.

## What replaces the check

`Glyndor/.github` v1.16.0 adds `scripts/verify-release-assets.py`, which
answers the same question where it is answerable: against the staged
files at release time, before `gh release create` makes them immutable.
#1431 wires it into `release.yml` — currently draft, waiting on this.

## Why not just update #1425

v1.14.1 is two minors behind now, and it still carries the broken
clause. Merging it would reintroduce the failure.

Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
Jaro-c added a commit to Glyndor/podup that referenced this pull request Aug 17, 2026
Consumer side of Glyndor/.github#133. **Draft on purpose — see Blocked
below.**

## Why

The asset-contract gate compares `install.sh` against `release.yml` and
answers *"do the two agree on the asset names"*. It cannot answer *"does
the release produce them"* — a workflow file is a statement of intent,
not proof that a step ran.

`Glyndor/.github` carried a check that tried to answer it anyway, by
looking for `SHA256SUMS` among the parsed `asset:` keys. Those keys are
the build matrix, and the manifest is computed *from* the matrix outputs
in a later step, so it can never match. **That is the false positive on
#1425** — the gate fails this repo for doing it correctly.

## What this does

Adds one step to the `release` job, between "Verify every signature
against the keys consumers embed" and "Create GitHub Release". At that
point everything is built, signed and checksummed, and the release is
not immutable yet — so existence is a stat call rather than an
inference.

What it requires is derived from `install.sh`'s own `${BASE_URL}`
fetches, not hardcoded. That matters: **the old check never looked at
`SHA256SUMS.sig`**, which `install.sh` downloads and without which it
aborts. A release missing only the signature would have passed a gate
whose stated purpose was making downloads verifiable.

## Verified, and not

Run locally against this repo's real `install.sh`, `install.ps1` and
`release.yml`:

| case | result |
|---|---|
| complete staging dir | `OK: all 8 files the install scripts fetch are
staged` |
| `SHA256SUMS.sig` removed | fails — the case the old check could not
see |
| `SHA256SUMS` removed | fails, naming both missing files |
| the amended gate, against this repo | passes (it did not before) |

**Not verified:** the workflow wiring itself. `release.yml` only runs on
a tag push, and dispatching it would cut a real release, so the step
order and the `upstream/` checkout path are unexercised until the next
release actually runs. I am not claiming otherwise.

## Blocked

The `ref:` currently pins the branch SHA of Glyndor/.github#133. That
SHA becomes unreachable once #133 is squash-merged, so **this must not
merge as it stands**. Order:

1. Glyndor/.github#133 merges.
2. `.github` cuts a tag.
3. #1425 bumps the `installer-contract` pin to that tag — that run is
what proves the gate fix green.
4. This PR's `ref:` moves to the tag, and comes out of draft.

---------

Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant