Skip to content

Commit 28fee2b

Browse files
author
CometAPI
committed
fix: harden immutable release verification
1 parent 58befcf commit 28fee2b

11 files changed

Lines changed: 601 additions & 190 deletions

.github/workflows/publish.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,8 @@ jobs:
286286
run: uv run python scripts/check_artifacts.py dist/*
287287
- name: Install and smoke-test each exact artifact
288288
run: uv run python scripts/check_clean_install.py dist/*
289+
- name: Verify the immutable release as a copied standalone repository
290+
run: python scripts/check_repository_independence.py
289291
- name: Record immutable artifact digests
290292
run: sha256sum dist/* > artifact-sha256.txt
291293
- name: Retain only the verified release bundle

AGENTS.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -157,14 +157,16 @@ Post-stable invariants:
157157
12. Keep `CHANGELOG.md` release-only: do not maintain an unmanaged `Unreleased`
158158
section. Record changes in Conventional Commits and let Release Please own
159159
the newest canonical dated release section after the changelog preamble.
160-
The version gate must reject any `Unreleased` level-two heading before merge
161-
or release.
160+
Use only canonical Markdown for level-two changelog headings; raw HTML H2
161+
parsing is renderer-dependent and must fail the version gate before merge or
162+
release. The same gate must reject any `Unreleased` level-two heading.
162163
13. A validated release-evidence block binds one canonical publication workflow
163164
run to its machine-readable identity marker and may contain no other Actions
164165
run URL. Keep preparatory implementation, CI, Release Please, failed-attempt,
165166
and recovery history outside that block. The document gate must reject
166-
non-canonical, wrapped, malformed, or contradictory workflow URLs regardless
167-
of prose or Markdown labeling.
167+
non-canonical, wrapped, encoded, control-obfuscated, malformed, or
168+
contradictory workflow URLs regardless of prose or Markdown labeling, and
169+
bind each source occurrence to exactly one rendered navigation destination.
168170

169171
## Repository independence
170172

@@ -289,8 +291,10 @@ workflow after maintainer authorization.
289291
Build wheel and source distribution into a clean output directory. Inspect
290292
their metadata and file lists, install each exact artifact independently
291293
outside the source tree, assert version and public imports, and run mocked-call
292-
smokes. Generated artifacts, local environments, and credentials must never be
293-
committed.
294+
smokes. Require every reviewed sdist member to match the release checkout byte
295+
for byte, and rerun the copied standalone repository verification in the
296+
immutable-tag build before retaining digests. Generated artifacts, local
297+
environments, and credentials must never be committed.
294298

295299
## Release and documentation rules
296300

ARCHITECTURE.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,9 @@ sdist, and PyPI metadata are immutable, the README uses an unversioned install
133133
command and publication-neutral release language that remains accurate before
134134
and after a release. Source-document and artifact checks reject approval,
135135
unpublished, exact-version installation, and versioned release-link text; each
136-
artifact long description must also exactly match the source README.
136+
artifact long description must also exactly match the source README. Artifact
137+
inspection additionally requires every reviewed source-distribution member to
138+
match the release checkout byte for byte.
137139

138140
Release Please v5.0.0 is pinned to the immutable commit whose action metadata
139141
uses `node24`. The workflow semantic contract fixes that SHA and runtime
@@ -146,7 +148,8 @@ Conventional Commits and never maintain an `Unreleased` placeholder. The
146148
version gate rejects that structurally incompatible placeholder, accepts Release
147149
Please's native linked form and legacy dated history, and validates repository,
148150
previous tag, candidate tag, and calendar date without rewriting generated
149-
history.
151+
history. Raw HTML level-two headings are rejected rather than interpreted with
152+
renderer-specific error recovery.
150153

151154
The PyPI publisher remains directly in `publish.yml` and is pinned to its
152155
reviewed Node 24 maintenance release. Pinning its exact SHA prevents a syntactic
@@ -167,9 +170,11 @@ validated release-evidence blocks in `ROADMAP.md` and `RELEASING.md`. Each
167170
block binds only the canonical publication run through its machine-readable
168171
identity; preparatory implementation, CI, Release Please, failed-attempt, and
169172
recovery history stays outside the block. The checker rejects every other run
170-
identity plus wrapped or malformed Actions URLs, so Markdown wording cannot
171-
disguise a contradictory workflow. Architecture documents mechanisms and
172-
boundaries, not a second historical ledger.
173+
identity and binds every source occurrence to exactly one rendered Markdown or
174+
HTML navigation destination after bounded normalization. Wrapped, encoded,
175+
control-obfuscated, malformed, or contradictory Actions URLs fail closed, so
176+
prose and renderer syntax cannot disguise a workflow identity. Architecture
177+
documents mechanisms and boundaries, not a second historical ledger.
173178

174179
The scheduled/manual default-branch smoke is an operational canary only; it
175180
does not prove the release commit. `COMETAPI_KEY` is exposed only to the

RELEASING.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -202,13 +202,16 @@ authorization failure.
202202
Conventional Commits carry pending changes, and Release Please owns the newest
203203
canonical dated section immediately after the preamble. The version gate rejects
204204
any unmanaged `Unreleased` level-two heading so the generated layout remains
205-
valid on every patch release.
205+
valid on every patch release. Use only canonical Markdown for level-two
206+
headings; raw HTML H2 semantics vary by renderer and therefore fail closed.
206207

207208
Each validated release-evidence block contains the immutable release identity
208209
and only its canonical publication workflow. Preparatory implementation CI,
209210
Release Please, failed-publication, and recovery history remains outside the
210-
block. The document gate rejects every different run identity and every wrapped
211-
or malformed Actions URL regardless of prose or Markdown labeling.
211+
block. The document gate binds every source occurrence to exactly one rendered
212+
navigation destination and rejects every different run identity plus wrapped,
213+
encoded, control-obfuscated, or malformed Actions URLs regardless of prose or
214+
Markdown labeling.
212215

213216
Release mode (`check_version.py --require-releasable-docs`) also fails closed
214217
until project authorship, the canonical GitHub repository URL, the copyright
@@ -225,7 +228,8 @@ description. The README therefore uses the unpinned
225228
PRs and post-release evidence changes must not introduce approval, unpublished,
226229
or exact-version availability statements. Artifact inspection applies the same
227230
policy to wheel `METADATA` and sdist `PKG-INFO`, so source and registry-facing
228-
descriptions cannot drift.
231+
descriptions cannot drift. Artifact inspection also requires every reviewed
232+
source-distribution member to match the release checkout byte for byte.
229233

230234
## Workflow responsibilities
231235

@@ -290,7 +294,9 @@ descriptions cannot drift.
290294
against the exact pre-publication digests and Trusted Publisher provenance
291295
before a clean install explicitly from `https://pypi.org/simple/`. The exact
292296
release live model is the canonical active model enforced by the workflow
293-
checker and cannot be overridden by repository variables.
297+
checker and cannot be overridden by repository variables. Before digest
298+
retention, the immutable-tag build also copies the checkout into an empty
299+
parent and completes the standalone repository verification there.
294300
Because the unused Release Please or recovery path is intentionally skipped,
295301
every job after the selector must use `always() && !cancelled()`, reject
296302
reruns, and require each direct dependency's result to equal `success`. This

0 commit comments

Comments
 (0)