Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
f64d35e
release: 3.8.0 — version, changelog, and why the number is MINOR
Aug 16, 2026
f1e9cea
audit(380): pre-registration, the falsification pass, and one pre-exi…
Aug 16, 2026
1b16e0f
fix(release): retract two false claims from the 3.8.0 section and two…
Aug 16, 2026
ad2c372
audit(380): F5 also fell, and the declared NFD gap was declared for a…
Aug 16, 2026
5109e86
audit(380): the retraction repeated the class it was retracting
Aug 16, 2026
2b851c0
audit(380): a rule I wrote forbids its own use — clarified with a mea…
Aug 16, 2026
6e87a0e
fix(release): der DOI wird zuletzt gepraegt, nicht zuerst
Aug 16, 2026
dac3fd5
fix(cli): beschriftete Zeilen der verify-Flaechen filtern Kontrollzei…
Aug 16, 2026
48a0215
fix: die Befunde der DEEP-Runde, und die Akte hat mehr davon getragen…
Aug 16, 2026
039ac5d
fix: die Delta-Runde — und die schwersten Funde trafen die eigene Arbeit
Aug 16, 2026
c9e94b3
audit(380): die Umkehrung, statt sie wieder zu vertagen
Aug 16, 2026
ec2a573
fix(release): vier Befunde am eigenen neuen Auslieferungspfad
Aug 16, 2026
13827c4
fix: sechs Linsen auf dem eingefrorenen Digest — zwei meiner Waechter…
Aug 16, 2026
8172b95
fix: die Gegenlesung hatte an einer von vier Stellen recht — und die …
Aug 16, 2026
a9b375c
audit(380): gemessen, was PR #139 am Release-Tor wirklich aendert
Aug 16, 2026
4cee1f3
fix(cli): die Steuerzeichen-Klasse als REGEL statt als Liste — und si…
Aug 16, 2026
42324c2
docs: zwei ausgelieferte Aenderungen, die der CHANGELOG nicht nannte …
Aug 16, 2026
377f8a8
audit(380): witnesses_ok=true bei NULL bestaetigenden Zeugen — und ke…
Aug 16, 2026
5624937
audit(380): ein Einstieg in die Akte — acht Dateien, 884 Zeilen, bish…
Aug 16, 2026
28cd806
audit(380): "alle fuenf" waren vier — die Klasse, die diese Akte beha…
Aug 16, 2026
751da26
audit(380): ein Tippfehler des Pruefers liest sich wie ein kaputtes A…
Aug 16, 2026
496ab8b
test: die Zahl im Akten-Index kann nicht mehr still veralten
Aug 16, 2026
8869167
docs(changelog): die sechs offenen Befunde stehen jetzt IM Release, n…
Aug 16, 2026
ab7e235
docs(changelog): ein Verweis zeigte auf etwas, das im sdist nicht exi…
Aug 16, 2026
9daeff8
fix(tests): vier Umgehungen der neuen Waechter, von der Gegenlesung b…
Aug 16, 2026
253405c
fix(tests): der Index-Waechter wird ausgeliefert, sein Gegenstand nic…
Aug 16, 2026
898dd08
test(mutation): der Hauptpfad hatte keinen Fail-open-Operator, der Ne…
Aug 16, 2026
eb72128
test: der Steuerzeichen-Waechter prueft jetzt zwoelf Klassen statt einer
Aug 16, 2026
7a244af
feat(tests): die Erwartungsvergleichs-Klasse geschlossen — sechs Flae…
Aug 16, 2026
cbcde03
feat(verify): die Origin-Bindung auf der Checkpoint-Flaeche — Owner-A…
Aug 16, 2026
49e9f64
fix(verify): zwei Defekte, die die 3-Linsen-Gegenlesung an der eigene…
Aug 16, 2026
d2fb722
feat(gate): eine ausfuehrbare Latte fuer das Vor-Tag-Tor — plus der V…
Aug 16, 2026
bb5b538
docs(adr): 0008 — der Vor-Tag-Eintrag wird eine signierte Attestierun…
Aug 16, 2026
abf33fa
feat(gate): der Pruefer fuer den signierten Vor-Tag-Eintrag — die Hae…
Aug 16, 2026
494f02d
fix(verify-proof): nicht messbar ist kein gemessenes Nein — `detail` …
Aug 16, 2026
8e53744
fix(never-raise): die Familie wird aus dem BAUM aufgezaehlt — und fin…
Aug 16, 2026
184fafc
feat(verify-proof): `signer_present` trennt den falschen Schluessel v…
Aug 16, 2026
585ee3e
fix(checkpoint): ein vkey, der sich selbst widerspricht, ist malforme…
Aug 16, 2026
8eb954e
merge: main in release/v3.8.0 — und mains Fassung gewinnt an zwei Ste…
Aug 16, 2026
15544f3
fix(never-raise): der Deckungs-Waechter war selbst unvollstaendig — e…
Aug 16, 2026
e0208e3
merge: PR #139 in den 3.8.0-Kandidaten — und die Eigenschafts-Latte f…
Aug 16, 2026
5effcbb
fix(tests): D1 des DEEP-Laufs widerlegt — die Erwartungsvergleichs-Kl…
Aug 16, 2026
0352cae
feat(audit): der DEEP-Lauf fuer 3.8.0 ist gefahren — ein Ziel widerle…
Aug 16, 2026
4921b62
fix(tests): die Gegenlesung fand einen Phantom-Treffer in meinem eige…
Aug 16, 2026
a4c027d
fix(mutation): two operators stopped disabling what their labels say
Aug 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 205 additions & 6 deletions .github/workflows/release.yml

Large diffs are not rendered by default.

20 changes: 19 additions & 1 deletion .github/workflows/reusable-build-attest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ on:
permissions:
contents: read

# Wie in release.yml und aus demselben Grund: ohne diesen Block laeuft jeder `run:`-Schritt unter
# `bash -e {0}`, also OHNE `pipefail` -- eine Pipeline meldet dann den Erfolg ihres LETZTEN Gliedes.
# Der Schritt darunter haengt zwei Pipelines an `sha256sum`; scheitert das erste Glied, schrieb er
# bisher eine leere bzw. unvollstaendige Pruefsummendatei und endete mit rc=0.
defaults:
run:
shell: bash

jobs:
build-attest:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -63,7 +71,17 @@ jobs:
id: digests
run: |
echo "sdist=$(sha256sum dist/*.tar.gz | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
sha256sum dist/* | tee dist/SHA256SUMS
# DERSELBE Fix wie in release.yml, im selben Durchgang. Diese Zeile schrieb
# `… dist/proofbundle-…tar.gz`; wer die Datei neben die Artefakte legt und
# `sha256sum -c SHA256SUMS` fahrt, bekam `No such file or directory`. Hier gefegt, weil
# dieser Workflow LIVE ist (published-artifact-gate.yml ruft ihn) und in
# docs/roadmap/FRONTLOAD.md als der Workflow steht, den release.yml uebernehmen soll --
# ohne diesen Durchgang holte die Uebernahme den Defekt still zurueck.
# Die Glob-Liste ist ABSICHTLICH aufgezaehlt und nicht `./*`: bei einem Wiederholungslauf
# im selben Arbeitsverzeichnis liegt SHA256SUMS bereits da und wuerde sich selbst mit
# hashen. Gleiche Form wie in release.yml, damit die dokumentierte Uebernahme nichts
# veraendert ausser dem Ort.
( cd dist && sha256sum ./*.whl ./*.tar.gz | sed 's| \./| |' | tee SHA256SUMS )

- name: Generate SLSA build provenance for the artifacts
if: ${{ !inputs.dry_run }}
Expand Down
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,15 @@ dist_pkgtest2/
dist_pkgtest3/
dist_pkgtest4/
dist_pkgtest5/
# GEMESSEN 2026-08-16: `dist_final/` und `dist_pkgtest6/` fehlten hier, und darin lagen drei
# VERFOLGTE 3.6.1-Artefakte mit zusammen 2 353 682 Bytes. Sie sind damit in JEDEM
# Quell-Archiv jedes Tags gelandet -- am echten GitHub-Archiv von v3.7.0 nachgemessen 19,3 %
# des unkomprimierten Inhalts -- und ueber den Zenodo-Webhook in den zitierbaren Datensatz.
# Im sdist waren sie NICHT: die MANIFEST.in-Allowlist hat gehalten. Der Befund betraf allein
# das Quell-Archiv, und genau das ist die Datei, die ein Aussenstehender herunterlaedt.
# Die Muster stehen einzeln statt als `dist*`, weil `dist_final` und `dist_pkgtest6` die
# einzigen zwei waren, die fehlten -- ein Sammelmuster wuerde auch kuenftige Verzeichnisse
# verschlucken, die jemand bewusst verfolgen will.
dist_final/
dist_pkgtest6/
dist_rc/
421 changes: 420 additions & 1 deletion CHANGELOG.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type: software
license: MIT
repository-code: "https://github.com/b7n0de/proofbundle"
url: "https://github.com/b7n0de/proofbundle"
version: 3.7.0
version: 3.8.0
doi: 10.5281/zenodo.21110642
keywords:
- cryptography
Expand Down
11 changes: 10 additions & 1 deletion RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ The order below is the convention, not a suggestion. A release is a fact about `
## Beta / pre-release (any future pre-release line)

Historical note: the 2.0.0b1–b3 line shipped this way until **2.0.0 final** (2026-07-09); the
stable default has since moved on to the 3.x line (current: 3.7.0) and the `[experimental]` extra
stable default has since moved on to the 3.x line (current: 3.8.0) and the `[experimental]` extra
ships with normal releases.
The checklist below is the convention for any FUTURE pre-release: `pip install proofbundle` never
pulls a PEP 440 pre-release, so the current stable stays the default while a preview stabilizes.
Expand Down Expand Up @@ -128,5 +128,14 @@ after the merge (see *Release ordering* above).
```bash
pip download proofbundle==X.Y.Z --no-deps -d /tmp/pb
sha256sum /tmp/pb/* # compare against the GitHub Release SHA256SUMS
# or, with SHA256SUMS downloaded next to the artifacts, let the tool do the comparing.
# --ignore-missing is required, not cosmetic: the command above fetches only the WHEEL, while
# SHA256SUMS lists the wheel AND the sdist, so a plain `-c` reports the sdist line as
# "No such file or directory / FAILED" on a perfectly good download. Measured, both failure
# modes still fail: a wrong digest exits 1, and an empty directory exits 1 with
# "no file was verified" — it does not pass silently when there is nothing to check.
( cd /tmp/pb && sha256sum --ignore-missing -c SHA256SUMS )
# to check both artifacts, fetch the sdist as well:
# pip download proofbundle==X.Y.Z --no-deps --no-binary :all: -d /tmp/pb
gh attestation verify /tmp/pb/proofbundle-X.Y.Z-py3-none-any.whl --repo b7n0de/proofbundle
```
10 changes: 10 additions & 0 deletions SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,16 @@ A conforming verifier MUST perform, in this order, and report each result:
expected root AND expected tree size ATOMICALLY (feeding check 8). A checkpoint
that does not verify FAILS the verdict — the expectations it would have carried
are never used unauthenticated.
**The signature authenticates THAT a log signed, not WHICH log** (since 3.8.0): a
checkpoint's C2SP origin line and the name in its signature block are separate
fields, and a signer MAY legitimately serve several origins under one key.
A relying party that pins only the verifier key therefore has NOT pinned the tree
it trusts. `--expected-origin` (CLI) / `expected_origin=` (`verify_witnessed_checkpoint`)
binds it; the comparison MUST be EXACT (no prefix, case-folding, trimming or
substring match). It is OPT-IN and defaults to unconstrained, matching
`verify-proof --expected-origin`: a verifier MUST NOT invent a default origin, and
a conforming implementation MUST report the origin it observed so an unpinned run
stays auditable.

The bundle **verifies** iff every performed check passes. Trust anchors (the
expected signer key, the expected Merkle root) are inputs the relying party
Expand Down
99 changes: 99 additions & 0 deletions audit_artifacts/380/00_INDEX.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# 3.8.0 pre-tag record — index

**There is no verdict for 3.8.0.** The gate (`scripts/pre_tag_audit_gate.py --strict`) is red, and it
must be: nothing in this directory attests a completed run. Read that as the state, not as an
omission.

**This file carries no discipline marker word**, on purpose. The gate on this branch grants a pass to
any non-negated marker line in any `.md` here, so an index that named the vocabulary would attest the
release by describing it. That property is itself one of the records below.

The 3.7.0 record was a single file. This one is twelve files, because the run kept finding things
about its own instruments — the last two are `PRE_REGISTRATION_DEEP_380.md`, the
frozen targets of the DEEP run, and `DEEP_RUN_RECORD_380.md`, its result. The guard below
caught this very sentence going stale twice, once for each of them. The order below is the order a reader needs, not the order they were written.

The count in that sentence read "eight" until it was measured. It is stated as "ten **files**" rather
than as a bare number so that the guard can check it: the number is now attached to the noun it
counts, which is the difference between a claim a test can hold and one only a careful reader can.

## Start here

| File | What it is |
|---|---|
| `PRE_REGISTRATION_380.md` | the targets, frozen before anything ran — plus four appended sections (§8–§11) correcting the run's own rules while it ran. §11 carries the current binding set and the rule that replaced an enumeration |
| `FALSIFICATION_F1_F7.md` | one lens: falsification-first with executable exploits. Seven targets stated, four hold, **three fell** — all three grading errors of mine, not defects in the release |
| `DEEP_RUN_RECORD_380.md` | the DEEP run's result and the attestation it carries. One target REFUTED (D1, the expectation-comparison population), five held. Read this before the findings |
| `PRE_REGISTRATION_DEEP_380.md` | the DEEP run's targets, frozen before it ran |
| `MESSUNG_das_tor_von_pr139.md` | why the release cannot be cut honestly on this branch's gate, and what PR #139 changes. Both gate versions measured side by side |

## Findings — each is a decision, not a note

All six were closed inside this release: two by Owner order (`OA-714ae03760`,
`OA-a41a514b63`, both answered "noch in 3.8.0") and four under the Owner's standing
instruction that all gaps be closed inside 3.8.0 — each against this record's own
recommendation to defer them. The
recommendation is kept in each file rather than overwritten: the Owner overruled a judgement call,
not an error of fact, and a record that silently adopts the decision it argued against is less
useful to the next reader than one that shows both.

| File | State | What it needs |
|---|---|---|
| `FINDING_erwartungsvergleich_klasse.md` | **closed** by `OA-714ae03760`, 7 of 7 members | was: six neighbouring comparison surfaces with no near-miss evidence. All six now run the shared corpus `tests/_beinahe_treffer.py`; each was verified by a rollback probe, and the pre-existing tests were measured to stay green under the same loosening — which is why the class was open |
| `FINDING_nachbarflaeche_ohne_origin_bindung.md` | **closed** by `OA-a41a514b63` | was: `verify --trusted-checkpoint` accepts a checkpoint from any log with no option to bind the origin. Now `--expected-origin` on the CLI and `expected_origin=` on `verify_witnessed_checkpoint`, six rollback probes. Closing it measured something the finding had not: a pinned *key* does not bind the origin either |
| `FINDING_json_trennt_die_drei_ursachen_nicht.md` | **closed** — **and this one is THIS release's**, see below | was: the `--json` path could not separate a wrong key from a tampered signature. Unlike the others the information seemed not to exist — a signature check is a two-input predicate. The KEY ID settles it: `signer_present` says whether the supplied key signed this note at all. The guard that pinned the collision predicted its own replacement and went red on schedule |
| `FINDING_quorum_erreicht_ununterscheidbar_von_keins_verlangt.md` | **closed** | was: `witnesses_ok: true` with zero confirming witnesses and no `threshold` in the output. The bound now ships with its boolean; the family was measured over every value-taking flag and this was its one member. Its deferral reason was overtaken, not wrong |
| `FINDING_never_raise_population.md` | **closed on the module axis** | the property now enumerates its family from the tree (36 listed modules vs 62 shipped; 79 surfaces vs 91). Closing it found a live defect nobody had predicted — `open()` reads an int as a FILE DESCRIPTOR, so `load_signer(123)` read a foreign open file instead of failing. The keyword-argument axis stays open and is named in the finding |
| `FINDING_pruefer_fehler_liest_sich_wie_artefakt_fehler.md` | **closed** | was: a typo in the verifier's own command line produced output byte-identical to "this file is not a proof". `detail` now reaches both output paths; all four causes separate. Re-measuring while closing moved the count from three colliding causes to two — an effect of this release's `threshold` field, written out rather than silently renumbered |

**FIVE of the six findings are on `main` and predate this release — exactly one is ours.** Two drafts of this
paragraph were wrong before this one: the first said "all five" (it was four of five), the second was
correct at the time and went stale the moment a sixth finding was added. That is the same error this
record spends most of its pages correcting — a summary label checked once and then carried — and it
is written out here rather than quietly renumbered. The second time it was caught by a counter-count
before the paragraph was published; the first time it was not.

Measured, per finding, by asking whether its subject exists at `v3.7.0`:

```
erwartungsvergleich (kbjwt: expected_aud != aud) v3.7.0 ja -> Altbefund
nachbarflaeche (verify --trusted-checkpoint) v3.7.0 ja -> Altbefund
json trennt drei ursachen (out["expected_origin"]) v3.7.0 NEIN -> THIS RELEASE
quorum (witnesses_ok) v3.7.0 ja -> Altbefund
never-raise population (_MODULES) v3.7.0 ja -> Altbefund
pruefer-fehler (--threshold, _tlog_failclosed) v3.7.0 ja -> Altbefund
```

The difference is not bookkeeping. For the five, the rule this run follows is that a `main` finding is
**reported**, not folded into a release that did not cause it. The third is different: `expected_origin`
in the JSON output is new **here**, the over-wide claim about what it separates was made **here**, and
its correction therefore belongs to this release rather than to a later one. It is corrected in place,
in the CHANGELOG and in the finding, with two guards that hold the measured state.

## What this run changed about itself

Worth stating because it is the honest summary: the subject held every measurement, and the
*instruments* did not. Two guards built during the run were shown by counter-reads to measure
nothing — one selected the wrong line of a fixture, one was bypassable by three rewrites. Both are
rebuilt and each carries a rollback probe. Seven numbers in these records were wrong and are
corrected in place with the measurement that replaced them, rather than quietly amended.

Two of the seven were in **this index**, and both survived the guard written to catch exactly this:
"the five findings" (there were six) and "this one is eight" (there were ten). The guard matched only
the phrasing `N of the M` — one grammatical form of the invariant — while two neighbours of that form
sat in the same file. That is the round's own lesson applied to the round's own instrument: fixing the
instance and not sweeping the neighbours. The guard now checks any count attached to `findings` or
`files`, and the sentences were rewritten to attach their numbers to those nouns.

The pattern behind most of it has one shape: **a number measured over one population and reported
over another.** It appears in the suite count (wrong environment), the file counts (wrong endpoint),
the sdist member count (no counting reproduces it), the violation count (one case not counted), and
the wrapped-call-site count (an enumeration read as complete).

## Reading order for an auditor

1. `PRE_REGISTRATION_380.md` §1–§7 — what was promised before anything ran.
2. `FALSIFICATION_F1_F7.md` — what was tried and what fell.
3. `PRE_REGISTRATION_380.md` §8–§11 — what the run had to correct about its own rules, in order.
4. The six findings, in the table above — all six closed, each with a rollback probe.
5. `MESSUNG_das_tor_von_pr139.md` — why the gate is red for the right reason only after #139.
Loading