Skip to content

Commit 93288ff

Browse files
wshallwshallwshallwshallclaude
authored
fix(release,ci): hold sigstore at 4.4.0 per owner ruling, make it stick, and audit the lock (BACKLOG #332) (#801)
* docs(backlog,adr): record #332's shipped mechanism, raise the sigstore version discrepancy (BACKLOG #332) Docs only. No build change. The #332 row said "not started". The mechanism shipped in a935480 (2026-08-29). Verified at 46ea10a on a clean tree, reading the working tree rather than piping git show: group at pyproject.toml:294, tracked lock (193 hashes), export + diff gate at security.yml:107-108, resync at dependabot-lock-resync.yml:140,152,156, --require-hashes install at release.yml:391, guard re-pointed at tests/test_ci_venv_pinning.py:204 and :994. Steps 2, 3 and 5 are done. Raised, NOT resolved: the row records an owner ruling of 2026-08-22 pinning sigstore==4.4.0, "NOT 4.5.0"; the shipped code pins 4.5.0 (pyproject.toml:295) and release.yml:385-390 argues the cooldown objection is spent. Both citations are live and they disagree. It needs an owner ruling, so both are recorded with the timeline and left alone. Also recorded: step 4's contamination risk did not materialise (measured with the CI-pinned uv==0.12.0, control-first); step 6 not started; and one new residual -- nothing pins release-tools.lock INTO the DEP-1 set, since the lockstep test is satisfied by set equality. ADR 0034's sigstore residual said the lock omits sigstore. That is now false, so the row is marked superseded rather than deleted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(release): hold the signing toolchain at sigstore 4.4.0, per owner ruling (BACKLOG #332) Owner ruling 2026-08-22, re-affirmed 2026-09-03 with the full history in hand: pin sigstore==4.4.0, NOT 4.5.0. a935480 (2026-08-29) landed the hashed-lock mechanism but authored the pin as 4.5.0, reasoning that dependabot.yml's 5-day cooldown had expired. That argument was published on PR 531 at 2026-08-26T23:46:18Z and RETRACTED BY ITS OWN AUTHOR at 2026-08-27T02:45:36Z, two days before it shipped. The retraction's own words: "I checked whether the ADR's premise had expired. I never checked whether the question had since been answered." The cooldown never reached the ruling in either direction. It expired 2026-08-02; the first ruling came twenty days later, and the 2026-09-03 re-affirmation had the expiry in the packet. Neither version has a patch line, so patch availability does not discriminate. Measured against PyPI 2026-09-03: 4.x is 4.0.0, 4.1.0, 4.2.0, 4.3.0, 4.4.0, 4.5.0 and nothing else. The 3.x series did ship patches, so a future 4.4.1 is possible. Re-export measured with the CI-pinned uv==0.12.0, control first (the unchanged tree re-exported byte-identically, so the instrument matches CI's): uv.lock moves one line, release-tools.lock three, and all six pre-existing DEP-1 artifacts stay byte-identical. No contamination. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(ci): make the sigstore 4.4.0 ruling stick, and audit the lock that backs it (BACKLOG #332) Two changes that only work together. 1. dependabot.yml gains `ignore: sigstore >=4.5.0`, so a routine weekly PR cannot re-propose the version the owner twice declined. Without it the ruling survives only until the next bot PR is merged, which is how it was undone the first time. 2. security.yml's audit step gains `pip-audit -r ci/locks/release-tools.lock`. This resolves the stated conflict at dependabot.yml's "NOT IGNORED, deliberately" policy rather than stepping over it. That policy keeps the `==` pins moving so an unattended pin cannot rot into "pinned, stale, unpatched". Its reason is about versions NOBODY HAS DECIDED, so it does not reach a version under an owner ruling -- there the weekly PR does not refresh neglect, it re-litigates a decision. The carve-out is recorded at the policy, with the condition for lifting it. The STALENESS half of the policy still binds, so it is honoured rather than waived. An `ignore` also suppresses the security track for the named range, which the file already accepts for annotated-types and ruff on the grounds that pip-audit still detects a withheld fix and reds a required gate. That backstop did not exist here: a935480 added release-tools.lock to the export and byte-diff set but not to the audit set, leaving the release signing closure the only committed lock nothing audited. Change 2 closes that. Each side's comment names the other, so removing one without the other is caught by reading either. Scoped to >=4.5.0, not to the package. sigstore 4.x has shipped zero patch releases but 3.x did, so a 4.4.1 is possible; this range leaves the 4.4.x patch track open. Also corrected: the exported-artifact count in the same file said SIX; it has been seven since a935480. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(backlog,adr,release): correct every record that argued for sigstore 4.5.0 (BACKLOG #332) The pin moved back to 4.4.0 in the two commits before this one. This commit fixes the records that still argue the other way, so a reader does not find the case for 4.5.0 sitting unmarked beside the 4.4.0 pin. release.yml's "ON THE VERSION" comment restated the cooldown argument. It now records the ruling, names the retraction, and says plainly not to re-derive the argument. ADR 0034 had two false assertions, both that sigstore is absent from every lock: the section 3 register row at :206 and the residuals row at :358, read at 46ea10a. Both are marked in place rather than deleted, per that table's own rule. A 2026-09-03 amendment records the sequence, the discharged re-evaluation clause, and the pip-audit gap this work closed. The backlog row cited :350 for that residual in four places. :350 is a blank line and was wrong at every commit; the citations now name the row and give the line with the commit they were read at. The #332 row records the ruling, the retraction timeline, the packet measurement showing the ruling never rested on the cooldown, the PyPI measurement showing neither version has a patch line, and the audit gap. It stays OPEN: step 6 is now the only thing left in it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: wshallwshall <mefordev@messagefoundry.org> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent aa11f21 commit 93288ff

8 files changed

Lines changed: 196 additions & 35 deletions

File tree

.github/dependabot.yml

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ updates:
77
# pyproject.toml + uv.lock and REGENERATES uv.lock IN its PRs (version updates GA 2025-03,
88
# security updates GA 2025-12) — the old "pip" ecosystem updated requirements/pyproject but NOT
99
# uv.lock. It still does not re-derive the EXPORTED locks (requirements.lock + docker/locks/* +
10-
# constraints.lock + ci/locks/* — SIX artifacts), which the DEP-1 gate in security.yml byte-diffs;
10+
# constraints.lock + ci/locks/* — SEVEN artifacts, since a9354808e added ci/locks/release-tools.lock),
11+
# which the DEP-1 gate in security.yml byte-diffs;
1112
# .github/workflows/dependabot-lock-resync.yml re-exports those on the Dependabot branch so the
1213
# gate stays green.
1314
#
@@ -56,11 +57,40 @@ updates:
5657
# `versioning-strategy: lockfile-only`. Both would freeze the hash-pinned CI toolchain, which ADR
5758
# 0034 §3 wants moving THROUGH this machinery — a pinned toolchain outside it rots into
5859
# "pinned, stale, unpatched — worse posture than floating".
60+
#
61+
# ONE CARVE-OUT, and it is the `sigstore` entry below (BACKLOG #332). The rule above is about
62+
# versions NOBODY HAS DECIDED: it keeps the toolchain moving because leaving a pin unattended is
63+
# how it rots. `sigstore`'s version is not unattended — it is an OWNER RULING, given 2026-08-22
64+
# and re-affirmed 2026-09-03, pinning 4.4.0 and naming 4.5.0 as the version not to take. So the
65+
# policy's own reason does not reach it, and the failure mode inverts: a routine weekly PR here
66+
# does not keep a neglected pin fresh, it re-proposes a version the owner has twice declined,
67+
# and merging it silently reverses a decision. That is not hypothetical -- it already happened
68+
# once, in a9354808e, which shipped 4.5.0 two days AFTER its own author had retracted the
69+
# argument for it (PR 531, comment of 2026-08-27T02:45:36Z). Lift this entry when the owner
70+
# rules differently in writing, and not before.
71+
#
72+
# THE STALENESS HALF OF THE RULE STILL BINDS, and is honoured by a different mechanism rather
73+
# than waived. The trade-off note above applies unchanged (an `ignore` suppresses the SECURITY
74+
# track for the named range too), so this entry is only acceptable while something else can see
75+
# an advisory in the pinned closure. Until this commit NOTHING could: security.yml's audit step
76+
# covered `requirements.lock`, `ci-scanners.lock` and `ci-quality.lock` but NOT
77+
# `ci/locks/release-tools.lock`, which a9354808e added to the export and byte-diff set without
78+
# adding to the audit set. That gap is closed in the same commit as this entry, so the
79+
# annotated-types / ruff bargain — detection untouched, a withheld fix reds a required gate
80+
# within ~24h, a human lifts the entry — now holds here too. If that audit line is ever removed,
81+
# REMOVE THIS ENTRY WITH IT.
82+
#
83+
# SCOPED TO `>=4.5.0`, NOT TO THE PACKAGE. sigstore 4.x has shipped zero patch releases (4.0.0,
84+
# 4.1.0, 4.2.0, 4.3.0, 4.4.0, 4.5.0; measured against PyPI 2026-09-03), but the 3.x series did
85+
# ship them (3.5.1, 3.6.7), so a 4.4.1 is possible. This range leaves the 4.4.x patch track open
86+
# and blocks only the minor bump the ruling declined.
5987
ignore:
6088
- dependency-name: "annotated-types"
6189
versions: [">=0.8.0"]
6290
- dependency-name: "ruff"
6391
versions: [">=0.16.0"]
92+
- dependency-name: "sigstore"
93+
versions: [">=4.5.0"]
6494
groups:
6595
# Version-update grouping (applies-to defaults to version-updates).
6696
python-deps:

.github/workflows/release.yml

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -375,19 +375,23 @@ jobs:
375375
# resolved at signing time.
376376
# 2. NO Dependabot ecosystem parses an inline `pip install X==Y` inside a workflow `run:`
377377
# block (.github/dependabot.yml registers uv, github-actions, npm). So the pin had no
378-
# updater, no trigger and no owner its own comment said "re-evaluate once it has aged
379-
# past the window" and nothing would ever have prompted that. It was 19 days overdue when
380-
# this landed. tests/test_ci_venv_pinning.py words the class: "a stale pin rots invisibly
381-
# and a DELETED pin is invisible twice over."
378+
# updater, no trigger and no owner: its own comment said "re-evaluate once it has aged
379+
# past the window" and nothing would ever have prompted that.
380+
# tests/test_ci_venv_pinning.py words the class: "a stale pin rots invisibly and a
381+
# DELETED pin is invisible twice over."
382382
# Routing it through `uv.lock` fixes both: the transitives are hash-pinned, and the group is
383383
# under the `uv` ecosystem Dependabot already watches.
384384
#
385-
# ON THE VERSION: the lock resolves sigstore 4.5.0, which the old inline pin deliberately
386-
# avoided. That choice was CORRECT when made — dependabot.yml sets a 5-day supply-chain
387-
# cooldown and 4.5.0 was then <48h old, so pinning the SIGNING toolchain to a fresher artifact
388-
# than the repo's own update policy allows would have inverted that policy at its
389-
# highest-privilege point. MEASURED: 4.5.0 published 2026-07-28T07:34:00Z, so the window
390-
# closed 2026-08-02. The objection is SPENT, not overridden.
385+
# ON THE VERSION: the lock pins sigstore 4.4.0 BY OWNER RULING -- given 2026-08-22, and
386+
# re-affirmed 2026-09-03 with the full history in hand. "NOT 4.5.0." The rationale lives
387+
# ONCE, at the `release-tools` group in pyproject.toml; BACKLOG #332 carries the record.
388+
# The one thing worth repeating HERE, because this is where the thought occurs to a reader
389+
# standing at the install: DO NOT RE-DERIVE THE COOLDOWN ARGUMENT. dependabot.yml's 5-day
390+
# window closed 2026-08-02 and BOTH rulings postdate it, so "the objection is spent" is not
391+
# news to this pin -- it is what the rulings were made in spite of. That argument has been
392+
# published and RETRACTED BY ITS OWN AUTHOR once already (PR 531, 2026-08-27), and shipped
393+
# anyway two days later in a9354808e before being corrected back. dependabot.yml now
394+
# carries a matching `ignore` entry so a bot cannot repeat it.
391395
python -m pip install --require-hashes -r ci/locks/release-tools.lock
392396
# Sign the wheel + sdist AND the SBOM + VEX, so an operator can verify the provenance of the
393397
# bill-of-materials and the exploitability assessment too — not just the code artifacts (ADR 0149).

.github/workflows/security.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,21 @@ jobs:
143143
# the advisory half not block, move the ci-quality line alone to its own step.
144144
pip-audit -r ci/locks/ci-scanners.lock --desc
145145
pip-audit -r ci/locks/ci-quality.lock --desc
146+
# THE SIGNING TOOLCHAIN, and it is the one that most needs this (BACKLOG #332). a9354808e
147+
# added ci/locks/release-tools.lock to the export set and the byte-diff gate above but NOT
148+
# here, so from 2026-08-29 until this line landed the release signing closure was the only
149+
# committed lock nothing audited -- hash-pinned, therefore sticky, therefore exactly the
150+
# "pinned, stale, unpatched is worse than floating" case the two lines above exist to stop.
151+
# It is also load-bearing for .github/dependabot.yml's `sigstore` ignore entry: that entry
152+
# suppresses the security track for >=4.5.0, and is only acceptable while this audit can
153+
# still see an advisory in the pinned closure. REMOVE THIS LINE AND YOU MUST REMOVE THAT
154+
# ENTRY -- the comment there says so from the other side.
155+
#
156+
# Same REQUIRED-context consequence as the two above, and a better-earned one: a CVE in
157+
# sigstore's closure reds the merge gate. That closure signs the wheel, the sdist, the SBOM
158+
# and the VEX with the job's OIDC identity, so blocking is the wanted behaviour, not a cost
159+
# reluctantly accepted for an advisory tool. `--ignore-vuln <ID>` remains the escape hatch.
160+
pip-audit -r ci/locks/release-tools.lock --desc
146161
# ANTI-SLOPSQUAT. Deliberately a STEP in this already-REQUIRED job rather than a new context: it
147162
# blocks today with no branch-protection change, the same reasoning as the ledger-gate backstop
148163
# in ci.yml. (A hard-failing job that is NOT a required context does not stop auto-merge — it

ci/locks/release-tools.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -235,9 +235,9 @@ securesystemslib==1.4.0 \
235235
--hash=sha256:a0743a3d978cf26e98a70a57e3fbd5a18e0a74c20cabe615f6a55b02ef0272b3 \
236236
--hash=sha256:faea87be0f9c4b4277a5fa1b54bf9bfd807be9a94ab11be6c557dc8b75c43285
237237
# via tuf
238-
sigstore==4.5.0 \
239-
--hash=sha256:020d3e07f622b2916bf453e66ff6ff0711e1fdc5ab69e8bd8902f71d9fcb316f \
240-
--hash=sha256:f045b207f2e12605cf775ec38e89c5eda625d71ffa7830477db65e47ec2bc8b2
238+
sigstore==4.4.0 \
239+
--hash=sha256:20ffe791c1fa33ce62148c0291b46280d29c1910964d9afac419e9b1a8afc56b \
240+
--hash=sha256:80c36d08b02479e2a282d0bea93de68fe0d43a93b0d58e4d9ac6bb9f8425957c
241241
sigstore-models==0.0.6 \
242242
--hash=sha256:5201a68f4d7d0f8bec1e2f4378eb646b084c52609a4e31db8c385095fff68b2e \
243243
--hash=sha256:c766c09470c2a7e8a4a333c893f07e2001c56a3ff1757b1a246119f53169a849

0 commit comments

Comments
 (0)