Skip to content

release: 3.8.0 candidate — version, changelog, and the measured reason for MINOR - #140

Merged
b7n0de merged 45 commits into
mainfrom
release/v3.8.0
Aug 17, 2026
Merged

release: 3.8.0 candidate — version, changelog, and the measured reason for MINOR#140
b7n0de merged 45 commits into
mainfrom
release/v3.8.0

Conversation

@b7n0de

@b7n0de b7n0de commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Release candidate 3.8.0 — not a release

This PR exists so CI can measure the candidate. It does not merge itself, tag, or publish: those are three separate Owner-GOs, and the standing GO (GO_OWNER_PB_RELEASE_371_NACH_WITHSTANDS_20260807) makes green CI precondition 7 of seven. CI triggers only on main and on pull requests against main, so without this PR that precondition cannot be measured at all.

Why 3.8.0 and not 3.7.1

The Owner chose the number on a measured recommendation. Of sixteen patch releases in this repository's history, fifteen carry no ### Added section. The one exception, 3.2.3, ships --strict on the internal maintenance script scripts/rust_parity_gate.py, not on the shipped CLI. A new user-facing CLI flag has never gone out in a patch here; the only ### Added section carrying CLI flags belongs to 0.2.0, a MINOR.

The counter-argument is recorded in the changelog entry rather than hidden: expected_origin has been in the library since 3.6 and the commit is titled fix(cli). Semver measures the surface, not the reason, and verify-proof --help now names a flag it did not name. The cost is asymmetric — someone pinning ~=3.7.0 would silently receive a new capability under 3.7.1.

What is in it

release/v3.8.0 branches from main at ac0688c and adds exactly one commit (f64d35e): the three version places the gate enforces, plus the changelog entry. The shipped delta over 3.7.0 is what already landed on main#136 (fixture), #137 (--expected-origin), #138 (the corrected ML-DSA reason), #135/#134/#132/#131/#130 (CI).

scripts/check_version_and_changelog.py: OK, version single-sourced, changelog current, no undelivered drift.

Deliberately not changed

RELEASE.md:49 and docs/readiness_pack/PROGRESS.md:3 both read current: 3.7.0. That is still true — 3.8.0 has no tag. Updating them here would assert a release that has not happened; they move with the tag. Likewise every since v3.7.0 line and the proof_7271 fixture manifest recording that proofbundle 3.7.0 performed the verification: those are statements about the past.

Measured locally on this tree

With [pq,pytest,test,dev,anchors]anchors beyond the usual set because CI runs [dev,pq,anchors] and without rfc3161_client 107 tests skip silently (measured: skipped=117 without, skipped=10 with):

ruff check .                EXIT=0
mypy src                    EXIT=0
unittest discover -s tests  EXIT=0 — Ran 2030 tests in 111.543s, OK (skipped=10)
mutation_check.py           STILL RUNNING at PR time — explicitly NOT a pass
make conformance-crossimpl  NOT YET RUN

The mutation gate and the cross-implementation gate are reported honestly as incomplete rather than assumed. The adversarial deep-gate run on the candidate digest (precondition 1) has not happened yet either.

kraxo and others added 2 commits August 16, 2026 12:51
The Owner chose 3.8.0 over 3.7.1 on a measured recommendation. The measurement,
so the number is auditable later: of sixteen patch releases in this repository's
history, fifteen carry no `### Added` section at all. The single exception, 3.2.3,
ships `--strict` on the internal maintenance script `scripts/rust_parity_gate.py`,
not on the shipped CLI. A new user-facing CLI flag has never gone out in a patch
here; the only `### Added` section carrying CLI flags belongs to 0.2.0, a MINOR.

The counter-argument is real and is recorded in the changelog entry itself:
`expected_origin` has existed in the library since 3.6 and the commit is titled
`fix(cli)`. But semver measures the surface, not the reason, and
`verify-proof --help` now names a flag it did not name. The cost is asymmetric —
someone pinning `~=3.7.0` would silently receive a new capability under 3.7.1,
while 3.8.0 harms nobody.

Three version places, exactly the ones the gate enforces:
  pyproject.toml, src/proofbundle/__init__.py, CITATION.cff  -> 3.8.0
`check_version_and_changelog.py`: OK, version single-sourced, changelog current,
no undelivered drift.

DELIBERATELY NOT CHANGED: `RELEASE.md:49` and `docs/readiness_pack/PROGRESS.md:3`
both read "current: 3.7.0". That is still TRUE — 3.8.0 has no tag. Updating them
now would assert a release that has not happened. They move with the tag, not
with this commit.

Also deliberately not changed: every "since v3.7.0" / "as of v3.7.0" line, and
the proof_7271 fixture MANIFEST recording that proofbundle 3.7.0 performed the
verification. Those are statements about the past; rewriting them would falsify
a record.

Measured on this tree with `[pq,pytest,test,dev,anchors]`:
  ruff check .                      EXIT=0
  mypy src                          EXIT=0
  unittest discover -s tests        EXIT=0 — Ran 2030 tests, OK (skipped=10)
  mutation_check.py                 still running at commit time — NOT a pass
  make conformance-crossimpl        NOT YET RUN

The `anchors` extra was added beyond the order's list because CI runs
`[dev,pq,anchors]`; without `rfc3161_client` 107 tests skip silently (measured:
skipped=117 without it, skipped=10 with it).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…sting finding

Three artefacts from the deep-gate run on the 3.8.0 candidate. None of them asserts
that the full N-lens jury ran -- `pre_tag_audit_gate` deliberately stays red, and
that was verified after each file was written rather than assumed.

PRE_REGISTRATION_380.md -- seven falsification targets, frozen before any of them
was measured, each stated as something that would make the release WRONG plus the
shape of the exploit that would show it. The three negative states including
`absent` are pre-registered explicitly: the default path is the one every existing
user is on.

FALSIFICATION_F1_F7.md -- all seven measured with executable probes, none fell.
The flag rejects a foreign origin (rc=1), omitting it leaves all seven verdict
fields identical, a mismatch stays distinguishable from a broken signature
(`inclusion_ok` remains true), seven near-miss origins are each rejected, four
hostile values return a verdict instead of raising, every changelog claim resolves,
and exactly three files carry the version.

One honest gap is declared in that file rather than left implicit: the NFD axis of
F4 was NOT exercised. The origin is pure ASCII, so the normalised form is identical
and the probe's own skip branch skipped it. Untested, not passed.

FINDING_never_raise_population.md -- an ALTBEFUND on main, reported and not
silently fixed, as the order requires. The never-raise family property is green
over a hand-maintained population of 36 modules while the package ships 50. Eleven
surfaces in seven modules never enter it, all matching the property's own name
pattern. Measured bidirectionally in throwaway copies: a planted raise in
`anchors.verify_anchors` (in the population) is caught, the identical plant in
`anchors_ots.verify_opentimestamps` (outside it) is not. The test is correct over
the set it walks; the set is smaller than the claim it carries.

One of the eleven is a live violation. `anchors_rfc3161.verify_rfc3161` still
raises AttributeError on a non-dict `frozen`/`rp_trust`, sixteen days after the
self-gate run first recorded it. It survived the 3.6.3 class fix because the
population never contained it, not because the fix was wrong.

The candidate's own delta (src/proofbundle/cli.py, +14 lines) is unaffected: its
seven targets were measured separately and each held.

The gate meta-test passed on the candidate's own change: a planted mutant that
drops `expected_origin` fails 2 of 5 tests, and the unmutated copy is green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
kraxo and others added 26 commits August 16, 2026 14:51
… audit grades

The pre-tag falsification pass was pointed at its own output. It found four
defects in this release candidate, all of them mine, and two of them in the
paragraph that justifies the version number.

CHANGELOG, claim 1 -- RETRACTED. "this repository has never shipped a new
user-facing CLI flag in a patch release (measured over sixteen patch releases)"
is false. Measured by diffing `add_argument("--...")` in src/proofbundle/cli.py
across the release tags: four patch releases shipped ten such flags.
  v3.1.0 -> v3.1.1  --expected-root-file --issuer-key --output --policy-id --valid-until
  v3.1.2 -> v3.1.3  --checkpoint-vkey --trusted-checkpoint --verification-time
  v3.2.1 -> v3.2.2  --require-derived-subject
  v3.2.2 -> v3.2.3  --eat (plus the `evalcard` subcommand)
The earlier reasoning had found `--strict` in 3.2.3, correctly established that
it sits on scripts/rust_parity_gate.py rather than the shipped CLI, and then
stopped -- without reading the rest of the same `### Added` section, where
`evalcard` and `show-eval --eat` stand, and without looking at 3.1.1, 3.1.3 or
3.2.2 at all. One explained-away hit was taken for a completed search.

The version number does not change, but its justification does, and the new one
is stronger than the old one was even when the old one was believed: SemVer
2.0.0 section 7 requires MINOR for new backward-compatible functionality in the
public API, `proofbundle` is a console entry point, so a new option on the
shipped CLI is exactly that. The project binds itself to SemVer in three places
(CHANGELOG header, GOVERNANCE.md, README.md). MINOR follows from the rule. The
precedent points the other way and no longer carries the argument; the cost
asymmetry (a `~=3.7.0` pin silently acquires a new capability under 3.7.1)
reinforces it rather than founding it.

CHANGELOG, claim 2 -- CORRECTED. "`verify_tlog_proof` has accepted
`expected_origin` since 3.6" is false; it has since 1.3.0. Measured:
`git show v1.3.0:src/proofbundle/tlogproof.py | grep -c expected_origin` -> 3,
continuous through v3.7.0, never removed and re-added. The v1.3.0 source carries
the release-review comment inline at line 155. PRE-EXISTING FINDING, REPORTED NOT
FIXED HERE: the same wrong wording sits in shipped source on main at
src/proofbundle/cli.py:949, from 911fd5c (PR #137, merged). The test file states
it correctly without a version number.

FALSIFICATION_F1_F7, grade F7 -- REGRADED holds -> FELL. The row claimed "a
repo-wide grep finds 3.8.0 in exactly the three enforced files and nowhere else".
False on its own digest: four files at f64d35e (the CHANGELOG too), seven at
f1e9cea. Worse, the target asked whether a FOURTH place carries a version, and
one does: pre_tag_audit_gate.py::_version_token computes `audit_artifacts/380/`
from the version at run time. Measured consequence -- the gate exits 1 and
TestF7PreTagAudit is the single red test in an otherwise green suite (1 failed,
1970 passed, 116 skipped), while main at ac0688c is green across 13 CI jobs.

FALSIFICATION_F1_F7, grade F6 -- REGRADED holds -> FELL. The target was "the
CHANGELOG claims something the tree does not do". It was answered by checking
that the named commits and files EXIST, which they do -- and the status paragraph
above them, carrying both false claims, was never read.

THE CLASS, because both regrades share it: a computed coupling is invisible to a
literal search. `audit_artifacts/380/` is version-coupled without the string
"3.8.0" occurring in it, so a grep for the version number is not a weak test of
that coupling but a structurally blind one. F7 was the target written to catch
exactly this, and it was answered with the grep.

Also corrected in the section: "for one reason only" / "genau ein Commit". Two
commits touch src/ (911fd5c and the release commit), MANIFEST.in grafts tests,
scripts, schemas, examples, conformance, formal and docs/readiness_pack into the
sdist where 27 files over 13 commits changed, and the dev extra narrows
ruff>=0.5 to <0.17 and mypy>=1.8 to <3. None of it is public API, so the verdict
stands, but the sentence was not accurate.

NEAR MISS, recorded because it would have been the worst outcome here: the first
draft of the retraction used the words "pre-tag adversarial audit" on a
non-negated line. pre_tag_audit_gate scans the [3.8.0] CHANGELOG section for
exactly that marker, so the sentence admitting the audit was incomplete would
have certified it as complete. Reworded, then MEASURED rather than reasoned:
gate ok=false, zero marker lines in the section, zero positive markers in all
three audit_artifacts/380 files. The gate stays red until a real verdict exists.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… wrong reason

Two more corrections to my own falsification pass, both found by pointing the
lenses at the audit record instead of only at the release.

F5 REGRADED holds -> FELL. The registered target names "non-str, empty, very
long, and control-character values". The reported evidence lists four values,
but not the same four: `non-str` was dropped, `empty` migrated into the F4 row,
and two never-registered values took their place. PRE_REGISTRATION_380.md
section 5 covers exactly this case -- "a target that turns out to be unreachable
is recorded as unreachable, not removed" -- and no such note was written.

The dropped class was not empty. Measured on the candidate against the real
fixture, positive control first so the harness is known to work
(ok=True log_ok=True inclusion_ok=True on the good call):

  int 123 / bytes / list / dict / nan   -> clean verdict ok=False
  object whose __eq__ raises            -> *** raw RuntimeError escapes

`tlogproof.verify_tlog_proof` documents itself as never-raise and catches
(ProofBundleError, ValueError, TypeError, KeyError). RuntimeError sits outside
that set. HONEST SEVERITY: the hostile object is supplied by the relying party's
own code, not over the wire -- type confusion in one's own configuration, not a
remote path. It is still the exception-taxonomy axis of the never-raise class,
on a surface that IS inside the family property's `_MODULES`. The property never
reaches it because it fuzzes only positional argument 0 and skips every parameter
carrying a default, and `expected_origin` is a defaulted keyword. Same blind axis
PR #141 opens for modules, one level down.

THE NFD GAP: fact right, conclusion convenient. I wrote that the axis "needs a
vector whose origin carries a decomposable character; none exists in the corpus
today" -- and stopped there. Every corpus origin is indeed pure ASCII. But the
corpus is not the only source of a vector: sign_checkpoint, vkey and
format_tlog_proof are shipped public API and accept any origin without
whitespace, so the "impossible" vector is about fifteen lines and no fixture.

Built and measured, positive control first:

  checkpoint in NFC:  expect NFC ok=True  | expect NFD ok=False | absent ok=True
  checkpoint in NFD:  expect NFC ok=False | expect NFD ok=True  | absent ok=True

The axis HOLDS -- the comparison is codepoint equality and nothing under src/
normalises an origin on this path. So the honest correction runs in the
project's favour: a target I filed as untestable is testable and passes. Under
section 5 that filing was a misfiling, not a gap.

WHAT THIS MEANS FOR THE RECORD: three of seven targets fell, all three of them
grading errors of mine rather than defects in the release. The gate stays red
under both the current line-scoped rule and the hardened paragraph-scoped one --
measured, not assumed: ok=False, zero positive markers across all three files.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A counter-read was pointed at yesterday's two correction commits and found four
false numbers in them. Every one is the same defect the corrected text warns
about two paragraphs higher: a number that does not say WHAT it counts.

  "27 files over 13 commits" in a sentence about the sdist
      MEASURED over exactly the seven grafted paths:  23 files / 8 commits
      27 = the same diff with the WHOLE docs/ instead of docs/readiness_pack,
           so it counts four files MANIFEST.in deliberately does not graft
      13 = the repository-wide --no-merges count, six of them ci: commits that
           touch neither src/ nor any grafted path
      Two numbers from two populations, neither of them the one named.

  "four patch releases shipped ten such flags ... measured by diffing
   add_argument("--...")"
      The named method yields SIXTEEN (6/3/2/5). Ten is correct only under an
      unstated second rule: distinct long-option NAMES not previously present
      anywhere in cli.py. Both are now given with their rule. "Four" holds.

  "green across all 13 CI jobs"
      That run has FOURTEEN jobs: 13 success, 1 skipped (branch-base). Thirteen
      is the successful subset sold as the total, and skipped is not green.

  "3 occurrences at v1.3.0"
      3 LINES, 4 occurrences -- line 157 carries it twice. The commit text had
      it right (grep -c -> 3); the record generalised the number past its unit.

Also corrected, same pass:

  * "section 5" as the source of "a target that turns out to be unreachable is
    recorded as unreachable, not removed". The quote is verbatim but lives in
    the PREAMBLE, line 5. Section 5 is the pre-sweep and says nothing of the
    kind. The wrong citation appeared twice.
  * "all corpus origins are ASCII" listed three. There are FOUR --
    tuscolo2026h2.sunlight.geomys.org under
    tests/fixtures/anchors/tlog_bitcoin_anchor/. All four are ASCII, so the
    conclusion survives; the SET was named short.
  * FINDING_never_raise_population.md still said "each held" about F1-F7 while
    FALSIFICATION_F1_F7.md said "THREE FELL", in the same directory. It also
    repeated the "one file" slip that the CHANGELOG had already corrected --
    the delta is TWO files under src/ (cli.py +14/-2, __init__.py +1/-1). Both
    fixed. A record that contradicts its neighbour is worse than an incomplete
    one: both halves look measured.
  * the Probe row pointed at scratchpad/falsifikation_380.py, which is untracked
    and unreachable from the record. The row now says so. The counter-read
    rebuilt F1/F3/F4/F5 from the prose and reproduced them exactly -- that is
    evidence the numbers are right, not evidence the record carries its proof.
  * the Candidate row pinned f64d35e without noting that the branch has since
    moved -- and it moved because of these very corrections. Now stated, with
    the consequence: no verdict travels with that pin.

WHAT HELD, measured independently by the counter-read: the 1.3.0 dating (single
introducing commit 457b6b8, never removed and re-added), the three SemVer
bindings, the console entry point, the MANIFEST.in graft list, the dev-extra
narrowing being new since v3.7.0, the RuntimeError escape at tlogproof.py:196
(with its own positive control, and checked for UNDERstatement too -- no
recursion path in the try block, so RecursionError is not a wire-side sibling),
the full NFD table, and the absence of any non-negated audit marker.

The substance of both corrections stands. Their arithmetic did not, and the
failure mode was the one they were written to fix. Recorded, not smoothed over.

Gate re-measured after every edit, under BOTH rules (the current line-scoped one
and the hardened paragraph-scoped one from #143): ok=False, zero positive markers
across all three files.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…surement, not a reading

Section 8 of the pre-registration says: "If the branch moves, the verdict does not
follow it -- a new digest needs a new run." Writing the pre-tag audit record IS a
branch move. Under the literal rule, recording a verdict invalidates the verdict
being recorded, and no release can ever be graded.

That is not a subtlety I reasoned my way to. It is what the sentence says, and I
wrote it. The rule does not distinguish a move of the CODE from a move of the
RECORD; when it was written the two had not come apart, and the stricter reading
looked free.

THE MEASUREMENT THAT SETTLES IT. Between the graded digest f64d35e and the branch
head:

  git diff --name-only f64d35e..<head> -- src/      ->  0
  git diff --name-only f64d35e..<head> -- tests/    ->  0
  git diff --name-only f64d35e..<head> -- scripts/  ->  0
  changed:  CHANGELOG.md + the three audit_artifacts/380/ files

The graded code is byte-identical. What moved is the record and the claims about
it -- and those moves ARE the corrections this falsification pass produced (F5, F6
and F7 fell, plus the four arithmetic errors a counter-read found in the first
correction). The run improved its own record, and the literal rule punishes exactly
that.

THE HOUSE FORM ALREADY ANSWERS IT. audit_artifacts/370/pre_tag_adversarial_audit_370.md
opens by naming the digest it graded ("run on the 3.7.0 release candidate, commit
02509ca") and is itself a later commit. The record names its subject rather than
pretending to be contemporaneous with it.

THE CLARIFIED RULE, appended as section 9 because the preamble forbids EDITING
anything below it -- section 8 stands unchanged:

  A verdict binds the src/ + tests/ + scripts/ tree of the digest it names.
  A commit that changes ONLY the record does not require a new run, and the record
  must NAME the digest it graded. A commit touching any of those three paths does
  require a new run, without exception.

Whether the code moved is a `git diff`, not a judgement call. That is the point:
the escape hatch is measurable, so it cannot be argued open.

HONEST LIMIT, written into the section itself: this is me loosening a rule I wrote,
in the run it governs, and that deserves the suspicion it invites. Two things bound
it -- the loosening is defined by a command anyone can reproduce, and it is narrower
than the 3.7.0 precedent it aligns with. It touches none of the seven preconditions
of the standing GO, and it makes a verdict out of nothing.

Gate re-measured after the append: ok=false. Still red, as it must be until a
verdict exists.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Owner-Entscheid 2026-08-16: "passe die doi reihenfolge so an das es optimal wird".

DIE GEMESSENE LAGE VORHER. `gh api repos/b7n0de/proofbundle/hooks` zeigt einen
aktiven Zenodo-Webhook auf `events=['release']`. Der Release-Schritt erzeugte das
GitHub-Release OHNE `draft:`, also sofort oeffentlich, noch im Job
`build-and-attest` -- der Webhook feuerte dabei. ERST DANACH ging `publish-pypi`
(`environment: pypi`, required reviewer) in die Freigabe-Warteschlange.

Belegt, dass der Weg lebt: Zenodo-Record 21721509 heisst
`corpus-review-2026-07-31-iter11`, exakt der letzte Release-Tag.

FOLGE: wurde die PyPI-Freigabe verweigert oder vergessen, existierte ein
permanenter, zitierbarer DOI fuer eine Version, die auf PyPI nie erschien. Ein DOI
ist nicht zuruecknehmbar. Die irreversible Aussenwirkung passierte VOR dem Tor, das
sie pruefen soll -- das ist keine Prozess-Meinung, sondern die Reihenfolge in dieser
Datei.

DIE AENDERUNG, zwei Teile:

  1. Das Release entsteht als ENTWURF (`draft: true`). Ein Entwurf feuert den
     `release`-Webhook nicht.
  2. Neuer Job `publish-release` mit `needs: [build-and-attest, publish-pypi]`
     dreht den Entwurf auf oeffentlich. Er ist der Schritt, der den DOI praegt --
     und er laeuft jetzt ZULETZT.

WAS PASSIERT, WENN DIE FREIGABE AUSBLEIBT: `publish-pypi` wartet, `publish-release`
laeuft nie, der Entwurf bleibt ein Entwurf. Kein DOI, kein oeffentliches Release,
kein "Latest", das auf eine Version zeigt, die es auf PyPI nicht gibt. Tag und
Attestierung bleiben -- die sind zurueckziehbar, ein DOI nicht. Genau deshalb liegt
die Grenze hier und nicht frueher.

FAIL-CLOSED: der neue Job prueft zuerst, dass der Entwurf ueberhaupt existiert
(`gh release view --json isDraft | grep -qx true`), bevor er ihn veroeffentlicht.
Fehlt er, ist etwas anderes schiefgegangen und es wird NICHTS blind publiziert.

EHRLICHE GRENZE, im Kommentar mitgeschrieben: das schuetzt gegen die REIHENFOLGE,
nicht gegen ein Versehen beim Freigeben. Wer die PyPI-Freigabe erteilt,
veroeffentlicht damit auch Release und DOI. Das ist gewollt -- es ist EINE
Entscheidung an EINER Stelle statt zwei an verschiedenen.

NICHT MESSBAR und deshalb hier genannt: ob Zenodo fuer einen Entwurf wirklich
schweigt, ist aus dem Repo nicht pruefbar. Die GitHub-Doku sagt, `release`-Events
mit `action: created` feuern fuer Entwuerfe nicht -- gemessen habe ich nur, dass der
Webhook auf `release` haengt und dass der letzte Deposit einen Release-Tag traegt.

Gemessen: YAML laedt, drei Jobs (build-and-attest, publish-pypi, publish-release),
`publish-release.needs == ['build-and-attest', 'publish-pypi']`, `draft: true`
gesetzt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…chen

Linse 2 der DEEP-Runde, gemessen: `_safe_line()` existiert in derselben Datei und
wird in `_cmd_verify` sechsmal benutzt -- in `_cmd_verify_proof` null Mal. Der
`origin` kommt aus der GEPARSTEN Beweisdatei, ist also vom Aussteller kontrolliert.

Der Spoof selbst ist aelter als dieses Release. NEU IN 3.8.0 IST DIE VERSCHAERFUNG:
der Zusatz `(expected …)` kommt aus der Erwartung des PRUEFERS, die der Angreifer
nicht schreiben kann -- und haengt damit hinter dessen gefaelschtem Text. Eine
erfundene Zeile las sich als ausdruecklich bestaetigter Origin-Treffer.

KLASSE, nicht Instanz: gemessen ueber alle acht verify-Funktionen benutzte nur
`_cmd_verify` den Filter. Mitgefixt sind die beiden anderen beschrifteten Zeilen
mit einem Verifizierer-Wert -- `sample-opening` und `enclave-attestation`
(`enclave.py:127` setzt `detail = f"malformed EAT token: {exc}"`, der
Ausnahmetext kann Token-Bytes tragen). Wo der Wert ohnehin fest ist, kostet der
Filter nichts, und er nimmt jedem kuenftigen Leser die Frage ab, ob GENAU DIESER
Wert fremdkontrolliert sein kann.

NICHT mitgefixt und bewusst so: `{'OK' if x else 'FAIL'}` ist ein Literal aus zwei
Moeglichkeiten, `ERROR: {exc}` geht nach stderr. Eine Fundstelle ist kein Defekt.

GEMESSEN: 14 passed (test_verify_proof_expected_origin + test_cli).
`_safe_line('ok\\x1b[2K…')` -> 'ok [2K …', Steuerzeichen zu Leerzeichen,
druckbarer Inhalt unveraendert.

EHRLICHE GRENZE: den End-zu-End-Spoof konnte ich NICHT nachbauen --
`sign_checkpoint` weist einen Origin mit Leerzeichen ab, und meine Spoof-Nutzlast
braucht welche. Die Filterwirkung ist gemessen, der vollstaendige Angriffspfad
uebernommen von Linse 2, nicht selbst reproduziert.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… als der Code

Owner-Entscheid 2026-08-16: "beheb auch gleichzeitig noch die anderen fehler die
aufgefallen sind". Sechs Linsen, ein Gate-Meta-Test mit neun Pflanzungen. Das
Ergebnis in einem Satz: der PRUEFGEGENSTAND hielt jeder Messung stand, die AKTE
UEBER IHN nicht.

## Am Code

ORIGIN-VERGLEICH (B/C aus dem Meta-Test). Zwei eingepflanzte Lockerungen --
`==` -> `.startswith()` und case-insensitive -- wurden von KEINEM der 2030 Tests
gefangen. Der Grund: beide Origin-Tests prueften nur einen VOELLIG FREMDEN Wert,
und dagegen verhaelt sich ein gelockerter Vergleich wie ein exakter. Der
Beinahe-Treffer fehlte, und er ist im Feld der gefaehrliche -- wer einen eigenen
Log betreibt, waehlt dessen Namen selbst.
  neu: OriginVergleichIstExakt mit 11 Beinahe-Treffern (Praefix, Suffix,
  Gross/Klein, fuehrendes/folgendes Leerzeichen, Zeilenumbruch, Schraegstrich,
  Schema, nur-Domain, leer) plus die Gegenrichtung, ohne die ein IMMER-FALSCH-
  Vergleich ebenfalls gruen waere.
  plus zwei Mutations-Operatoren in scripts/mutation_check.py.
  GEMESSEN, beide einzeln gefahren: startswith -> 3 rot, casefold -> 2 rot,
  sauber wieder gruen. Ein Korpus ohne Mutant ist eine Behauptung ueber sich selbst.

KONTROLLZEICHEN. `_safe_line()` steht in derselben Datei und wurde in
`_cmd_verify` sechsmal benutzt, in den sieben anderen verify-Funktionen null Mal.
Der `origin` kommt aus der geparsten Beweisdatei. Mitgefixt: `sample-opening` und
`enclave-attestation` (`enclave.py:127` setzt `detail = f"malformed EAT token:
{exc}"`). NICHT mitgefixt und bewusst: `{'OK' if x else 'FAIL'}` ist ein Literal,
`ERROR: {exc}` geht nach stderr -- eine Fundstelle ist kein Defekt.

DAS VERDIKT NENNT DIE ERWARTUNG. Gemessen lieferten drei verschiedene Ursachen --
fremder Origin, falscher log-vkey, verfaelschte Signatur -- BYTE-IDENTISCHES JSON
(gleicher sha256). `inclusion_ok` bleibt in allen dreien True und trennt nichts.
Neu: `expected_origin` im JSON, `None` wenn nicht gefragt (nicht Leerstring --
"nicht gefragt" und "gefragt und leer" sind zwei Lagen).

DER TREFFER-ZWEIG hatte keinen Waechter: der einzige Text-Modus-Test fuhr den
FEHLSCHLAG, alle anderen `--json`. `" (expected)"` war die einzige Verhaltenszeile
des Release ohne Zusicherung.

DER FALSCHE KOMMENTAR IM AUSGELIEFERTEN QUELLTEXT. `cli.py:949` sagte
"expected_origin since 3.6". Gemessen: `git log -S expected_origin --
tlogproof.py` nennt genau einen einfuehrenden Commit, 457b6b8 = v1.3.0. Der
CHANGELOG hatte recht, der Quelltext nicht -- und er wird ausgeliefert.

## An der Auslieferung

DER DOI WURDE VOR DEM OWNER-TOR GEPRAEGT. Gemessen: aktiver Zenodo-Webhook auf
`events=['release']`, das Release entstand ohne `draft:` VOR `publish-pypi`.
Jetzt: Entwurf -> PyPI-Freigabe -> `publish-release` dreht ihn oeffentlich. Bleibt
die Freigabe aus, gibt es keinen DOI. Fail-closed.

2 353 682 BYTES FREMDER 3.6.1-ARTEFAKTE lagen verfolgt in `dist_final/` und
`dist_pkgtest6/` -- am echten GitHub-Archiv von v3.7.0 nachgemessen 19,3 % des
unkomprimierten Quell-Archivs, und ueber den Webhook im zitierbaren Datensatz. Im
sdist waren sie NICHT (die MANIFEST.in-Allowlist hielt). Entfernt + gitignored.

SHA256SUMS trug den `dist/`-Praefix, und RELEASE.md schickt Nutzer genau damit
pruefen: `sha256sum -c SHA256SUMS` meldete fuer beide Zeilen "No such file or
directory / FAILED". Jetzt relativ, Probe gefahren: beide Zeilen OK.

DOKUMENTATION. `--expected-origin` kam in README, SPEC.md, RELEASE.md,
INTEGRATIONS.md und CROSS_IMPLEMENTATION_REPORT.md null Mal vor. Das Release machte
damit `docs/TRUST_ANCHORS.md` unvollstaendig -- die Zeile nennt sich selbst "the
whole trust surface" und war VOR 3.8.0 vollstaendig, weil es keine CLI-Form gab.
Ergaenzt dort und im ausgearbeiteten Beispiel in PUBLIC_TRANSPARENCY_PROFILE.md.

PROSA-VERSIONEN. RELEASE.md und docs/readiness_pack/PROGRESS.md auf 3.8.0. Das zog
eine Manifest-Drift im readiness_pack nach sich (PROGRESS.md ist gepinnt) -- neu
erzeugt, `--check` OK. Der oeffentliche Schluessel wechselte dabei; das ist Bauart,
der Docstring sagt "signed with an EPHEMERAL key generated at [generate time]".

## An der Akte -- und hier lag das meiste

DIE SUITE-ZAHL STAMMTE AUS DER FALSCHEN UMGEBUNG. Die Akte deklarierte
`[pq,pytest,test,dev,anchors]` und berichtete daraus `1 failed, 1970 passed, 116
skipped`. Gemessen sind 113 der 116 Skips `[anchors]`-gegated -- mit installiertem
Extra waeren sie GELAUFEN. Die Zahl kam aus einer Umgebung ohne. Genau dieser
Fehlermodus ist in §4 als RT-08 praeregistriert; die Akte hat ihn benannt und im
selben Lauf begangen.
  NACHGEMESSEN mit `[dev,eval,anchors,pq]`: 1 failed, 2085 passed, 9 skipped.

MEINE EIGENE LOCKERUNG §9 WAR WEITER ALS BEHAUPTET. Sie band das Verdikt an
`src/ + tests/ + scripts/` und nannte das "der Code". ZWEI der DREI Orte, die
`check_version_and_changelog` als Versions-Wahrheit erzwingt, lagen ausserhalb --
`pyproject.toml` und `CITATION.cff`. Ausfuehrbar gezeigt an `ed8c3b5`, das IN
diesem Delta liegt: es wechselt den blockierenden Linter und die §9-Messung meldet
sauber. Der Satz "the escape hatch is measurable, so it cannot be argued open" ist
als Formulierung falsch -- die Luke muss nicht aufargumentiert werden, sie war
konstruktiv offen.
  Abschnitt 10 angehaengt (§9 bleibt stehen): die Bindemenge umfasst jetzt auch
  conformance/ schemas/ formal/ examples/ pyproject.toml CITATION.cff MANIFEST.in
  .github/workflows/ -- mit einer REGEL statt einer Liste, und mit der ehrlichen
  Grenze, dass die dauerhafte Form die Umkehrung waere (alles bindet ausser einer
  begruendeten Ausschlussliste).
  ERSTE MESSUNG unter der korrigierten Regel: `release.yml` und `cli.py` HABEN
  sich seit f64d35e bewegt. Das Verdikt bindet diesen Digest also nicht mehr --
  die richtige Konsequenz meiner eigenen Korrektur.

WEITERE FALSCHE ZAHLEN, je mit der Messung korrigiert:
  "27 Commits auf #139"  -> reproduziert NUR gegen eine veraltete lokale Ref vom
                            12.08.; zum Freeze 28, heute 29 mit / 27 ohne Merges
  "23 Dateien / 8 Commits" -> Dateien stimmen; die 8 zaehlt Merges mit, ohne sie
                            sind es 6, und die danebenstehende 13 hatte ausserdem
                            einen anderen Endpunkt. Drei Unterschiede auf einmal,
                            keiner genannt.
  "all seven reported fields" -> es sind acht; die Aufzaehlung liess ausgerechnet
                            `witnesses` weg. Seit dieser Runde neun.
  "11 Flaechen / 7 Module" -> top-level-only; mit Unterpaketen 12/8. Der Sweep,
                            der eine handgepflegte Liste als zu eng entlarvt, war
                            selbst zu eng.
  FINDING nannte keinen Digest, obwohl §9 es verlangt -> nachgetragen (f64d35e
                            bzw. ac0688c fuer die main-Messungen).

DIE KLASSE HINTER ALLEN: eine Zahl ueber eine Population messen und ueber eine
andere berichten. Der CHANGELOG benennt sie selbst -- und begeht sie danach
viermal weiter. Der Klassen-Fix waere, dass jede Zahl ihren Befehl mittraegt, so
wie `pyproject.toml:104-106` es mit dem gepinnten Baum vormacht.

## NICHT behoben, bewusst, mit Owner-Entscheid

(F) Eine 12-Byte-Datei mit dem Wort `adversarial` kippt `pre_tag_audit_gate` von
MISSING auf OK. Der Riegel misst Prosa, nicht Wahrheit; in dieser Bauform ist das
nicht reparierbar. Der Fix waere ein runner-signiertes Receipt ueber die
auditierte SHA -- Tage, nicht Stunden.
(E) Kein Semver-Oberflaechen-Gate: eine Patch-Nummer fuer ein Release, das die CLI
aendert, faellt durch alles.
Owner 2026-08-16: beide als Befund fuehren.

## Gemessen, Abschluss

  volle Suite ([dev,eval,anchors,pq])  1 failed, 2085 passed, 9 skipped
    der eine rote ist der BEABSICHTIGTE Audit-Eintrag (TestF7PreTagAudit)
  ruff                                 All checks passed
  check_version_and_changelog          OK
  doc_link_check                       PASS, 91 Links, 0 broken
  claims_hygiene                       PASS, 49 Docs, 0 Verstoesse
  readiness_pack_manifest --check      OK
  type_confusion_gate --strict         never_raise_ok=True
  test_manifest_gate                   2095 gesammelt (Boden 1750)
  pre_tag_audit_gate --strict          exit 1  <- MUSS rot sein, kein Verdikt

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fuenf Delta-Linsen auf 48a0215, nachdem die korrigierte Bindemenge (Abschnitt 10)
zeigte, dass das Verdikt auf f64d35e diesen Stand nicht mehr bindet. Ergebnis in
einem Satz: der Kandidat haelt, die HAERTUNG DIESER RUNDE hatte keinen Waechter,
und drei ihrer Begruendungen waren falsch gemessen.

## Die Sicherheits-Haelfte hatte NULL Tests

Eine Gegenlesung hat alle vier neuen `_safe_line`-Umwicklungen zurueckgenommen und
die volle Suite gefahren: unveraendert. Der einzige vorhandene Test prueft die
FUNKTION isoliert, keine Zusicherung prueft eine AUFRUFSTELLE. Die andere Haelfte
desselben Commits bekam drei Testklassen, ausdruecklich weil ihre Zweige ungedeckt
waren -- die sicherheitsrelevante Haelfte bekam nichts.

  neu: SteuerzeichenKoennenKeineZeileFaelschen -- zwei Ende-zu-Ende-Tests (Wert aus
  der Beweisdatei, Wert aus argv), jeder mit Gegenprobe des Messaufbaus, plus ein
  Pin auf alle vier Stellen.
  RUECKNAHME-PROBE gefahren, je Umwicklung einzeln entfernt:
    enclave-attestation -> 1 rot · sample-opening -> 1 rot
    log-signature -> 2 rot · expected -> 2 rot
  Die zwei erreichbaren Stellen roeten DOPPELT (Verhalten + Pin) -- der Beleg, dass
  die Verhaltenstests nicht leerlaufen. Baum danach sauber.

  Der Pin fiel beim ersten Lauf an `expected` -- zu Recht. Er sah nur print-Argumente,
  und dort steht die Umwicklung in der Zuweisung an `origin_note`. Ein Waechter, der
  die Gestalt statt die Sache misst, findet die Sache nicht, wo sie anders geformt
  ist. Jetzt ueber ALLE f-Strings.

## Drei falsch gemessene Begruendungen, alle meine

DIE DREI URSACHEN SIND NICHT UNTERSCHEIDBAR. Der Commit, der `expected_origin` ins
JSON legte, las sich, als schloesse das die Luecke. Gemessen: mit gesetztem Flag
liefern fremder Origin, falscher log-vkey und verfaelschte Signatur BYTE-IDENTISCHES
JSON -- das Feld echot die EINGABE des Pruefers, und die ist in allen drei Faellen
dieselbe. Der zugehoerige Test war am Stand OHNE den Fix gruen: er verglich zwei
Faelle, die nie gleich waren.
  ersetzt durch die schmalere WAHRE Eigenschaft (gefragt vs. nicht gefragt, mit
  Gegenprobe dass sich sonst nichts unterscheidet) + einen Waechter, der den
  GEMESSENEN Stand festhaelt und rot wird, wenn die Luecke je wirklich zugeht.
  Befund: audit_artifacts/380/FINDING_json_trennt_die_drei_ursachen_nicht.md

DER ENTWURF FEUERT DAS WEBHOOK SEHR WOHL. Der Kommentar in release.yml sagte "Ein
Entwurf feuert ihn NICHT". GitHub dokumentiert die Aktion `created` woertlich als
"A draft was saved". Was nicht passiert, ist der DEPOSIT: Zenodo handelt auf dem
veroeffentlichten Release -- gemessen ueber sieben Deposits, Record 4-8 s nach dem
Veroeffentlichen, nie waehrend der Entwurfsphase, laengster Entwurf 7 d 20 h. Die
Schlussfolgerung haelt, der Grund war falsch, und der Schutz haengt damit an ZENODOS
Verhalten statt an GitHubs Schweigen. Steht jetzt so in der Datei.

`sha256sum -c` KAM IM GANZEN REPO GENAU EINMAL VOR: in meinem eigenen Kommentar, der
behauptete, RELEASE.md schicke Nutzer damit pruefen. RELEASE.md nannte einen
Sichtvergleich. Statt die Begruendung zu streichen ist sie WAHR gemacht -- RELEASE.md
bietet den Befehl jetzt an. Ein Format-Fix ohne ein Kommando, das ihn nutzt,
verbessert niemandes Lage.

## Am Auslieferungspfad

DIE ID STATT DES TAGS. `gh release view <tag>` laesst REST und GraphQL um die Wette
laufen und nimmt, wer zuerst antwortet -- undefiniert, wenn ein veroeffentlichtes und
ein Entwurfs-Release denselben Tag tragen. Dieses Repo war in dem Zustand (je zwei
Zenodo-Records auf iter10 und v2.0.0). Der falsche Treffer haette einen ZWEITEN
permanenten DOI gepraegt, also genau das, was die Umstellung verhindern soll.
  jetzt: Release-ID als Job-Output, Aufloesung per `gh api releases/<id>`.
  DREI Zustaende: Entwurf -> veroeffentlichen · schon oeffentlich -> Erfolg
  (ein roter Wiederholungslauf bei korrektem Endzustand fuehrt dazu, dass jemand den
  Riegel entfernt) · unlesbar -> BLOCK.
  `make_latest` war durch die Umstellung tote Konfiguration (Entwuerfe koennen nicht
  "latest" sein, und der Zweig, der es nachgezogen haette, wird bei draft:true
  uebersprungen) -- es wird jetzt im PATCH gesetzt.
  neuer Job `orphan-draft-notice`: bleibt die PyPI-Freigabe aus, ist der liegende
  Entwurf der RICHTIGE Endzustand, aber er darf nicht der stille sein.

pipefail als KLASSEN-FIX, nicht als Zeilenreparatur: `defaults.run.shell: bash` auf
Workflow-Ebene in beiden betroffenen Dateien. Ohne ihn laeuft jeder Schritt unter
`bash -e` und eine Pipeline meldet den Erfolg ihres letzten Gliedes -- ein
fehlendes Wheel schrieb ein unvollstaendiges SHA256SUMS mit rc=0. Eine Aufzaehlung
der heute betroffenen Zeilen waere am naechsten Schritt schon unvollstaendig.
  SWEEP ueber alle neun Workflows, mit Gegenprobe des Musters (8 Treffer auf
  release.yml, zweite unabhaengige Zaehlung bestaetigt): die anderen sieben haben
  gar keine Pipes. Klasse vollstaendig, nicht nur die zwei aufgefallenen.

Der Klassen-Nachbar `reusable-build-attest.yml` trug denselben `dist/`-Praefix UND
kein pipefail. Er ist LIVE (published-artifact-gate ruft ihn) und steht in
FRONTLOAD.md als der Workflow, den release.yml uebernehmen soll -- ohne diesen
Durchgang holte die Uebernahme den Defekt still zurueck.

## Am oeffentlichen Dokument

Der neue Absatz in PUBLIC_TRANSPARENCY_PROFILE.md stand INNERHALB des ```bash-Zauns
(Zeilen 90-93, Zaun 82-94) und rendert auf GitHub und im Zenodo-Deposit als
Shell-Code. Dazu deutsch in einem durchgehend englischen Dokument. Beides behoben.
  SWEEP ueber 81 Dokumente auf Zaun-Balance und Sprache: 0 weitere. MIT Gegenprobe,
  weil drei Nullen wie ein toter Messaufbau aussehen -- der Detektor feuert am
  zurueckgenommenen Originalabsatz (7 Treffer) und der Zaun-Zaehler an einem
  kuenstlich kaputten Beispiel.

## Am CHANGELOG

"one behavioural change" war falsch: es sind VIER (Flag · neuer JSON-Schluessel in
JEDER Ausgabe · drei geaenderte Textzeilen · SHA256SUMS-Format + Entwurfs-Reihenfolge).
"existing invocations are unaffected" verwechselte VERDIKT mit AUSGABEFORM -- das
Verdikt bleibt, die Form nicht. Beides korrigiert statt still ersetzt.
  neu: ### Security (die Steuerzeichen-Haertung war nirgends dokumentiert) und drei
  CI-Eintraege (DOI-Reihenfolge, SHA256SUMS, die 2,35 MB entfernter Fremdartefakte).
  "23 Dateien / 8 Commits" nannte EINEN Befehl fuer ZWEI Zahlen; er liefert 8, nie 23.

## Gemessen, Abschluss

  volle Suite    1 failed, 2089 passed, 9 skipped, 41 subtests  (142 s)
                 der eine rote ist der BEABSICHTIGTE TestF7PreTagAudit
                 die 17 Tests der Origin-Datei einzeln verifiziert: 17 PASSED,
                 KEIN SKIPPED -- auch der Signatur-Verfaelschungspfad lief wirklich
  Beinahe-Treffer 11 -> 17 (vollbreite Zeichen, Host-Punkt, //, Prozent-Kodierung)
                 ersetzt den tautologischen Normalform-Test, der vier subTests auf
                 DIESELBE ASCII-Zeichenkette fuhr und rc==0 zusicherte -- er trug den
                 Namen des Defekts, den er strukturell nicht finden konnte
  ruff           All checks passed
  doc_link_check PASS, 91 Links, 0 broken
  claims_hygiene PASS, 49 Docs, 0 Verstoesse
  check_version  OK
  pre_tag_audit  rc=1 <- MUSS rot sein, es gibt kein Verdikt
                 gegengeprueft: der neue Befund traegt KEIN Markerwort, das Tor
                 kann durch ihn nicht kippen

OFFEN, bewusst: das Verdikt. Der Kandidat ist seit f64d35e viermal gewandert; nach
der Regel, die die Akte sich selbst gibt, braucht ein neuer Digest einen neuen Lauf.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Abschnitt 10 endete mit "die dauerhafte Form waere die Umkehrung … und die
gehoert in den naechsten Lauf". Das war derselbe Fehler, den Abschnitt 10 an
Abschnitt 9 korrigiert: eine Instanz erweitern und den Klassen-Fix vertagen.
Eine Gegenlesung hat die erweiterte Liste gegen IHRE EIGENE Regel gemessen und
vier Verstoesse gefunden, einen davon live.

ABSCHNITT 11 — alles bindet, ausser einer begruendeten Ausschlussliste.
  Der Live-Verstoss: `docs/readiness_pack/` steht als `graft` in MANIFEST.in:26,
  ist also ausgelieferter Inhalt, war ausgeschlossen UND hat sich seit f64d35e
  bewegt (4 Dateien). Dazu README.md (= long_description auf PyPI, also
  "Metadaten"), fuzz/ + .clusterfuzzlite/ (= "Pruefkorpus"), action/action.yml
  (an Dritte ausgeliefert).
  Der eigentliche Klassen-Fix ist NICHT die laengere Liste, sondern der WAECHTER
  UEBER der Liste: kein graft/include-Pfad aus MANIFEST.in darf von der
  Ausschlussliste erfasst sein. Gemessen 8 Eintraege, davon erfasst null.
  Keine zweite Messstelle — MANIFEST.in wird gelesen, nicht nachgebildet.
  GEMESSEN: 21 geaenderte Dateien seit f64d35e, davon bindend 15 nach der neuen
  Regel gegen 5 nach der alten Liste. Die drei entfernten dist_*-Artefakte waren
  VERFOLGT und lagen in jedem Zenodo-Deposit — auf keiner Liste. "Alles ausser"
  haette sie vom ersten Tag an gebunden.

ZWEI FAKTENFEHLER in FALSIFICATION_F1_F7.md, beide von einer Gegenlesung gefunden:
  Zeile 41 belegte F7 mit `1 failed, 1970 passed, 116 skipped` — genau der Zahl,
  die Zeile 12 derselben Datei als aus der falschen Umgebung stammend
  zurueckzieht. Eine Datei, eine Zahl, einmal widerrufen und einmal als Beleg.
  Die F2-Korrektur machte den Satz falsch, neben dem sie steht: mit NEUN Feldern
  unterscheidet `expected_origin` sehr wohl "Flag abwesend" von "Treffer". Der
  registrierte Satz von F2 meint den Stand VOR dem Release und haelt; was fiel,
  ist die Belegformulierung. Und ihre Begruendung ("damit drei Ursachen
  unterscheidbar werden") ist gemessen falsch.

FIXTURE-HERKUNFT: MANIFEST.json sagte "recorded --json output … 3.7.0". Die
Version ist genannt, die Angabe also nicht falsch — aber wer sie heute nachfaehrt,
bekommt eine andere Datei. Nachgemessen mit der DOKUMENTIERTEN Aufrufform
(--threshold 4 + 5 Zeugenschluessel; der erste Versuch liess sie weg und mass
damit etwas anderes): jeder Schluessel reproduziert mit gleichem Wert, GENAU EINER
kommt hinzu (`expected_origin`). Als Notiz eingetragen; die fuenf eingefrorenen
Dateien sind unberuehrt — die Fixture ist Bezugspunkt mehrerer Akten, und eine
Fixture, die mit dem Code wandert, kann dem Code nicht widersprechen.

Gegengeprueft: pre_tag_audit_gate rc=1 (bleibt rot, kein Verdikt), kein neues
Markerwort in den Akten-Dateien, doc_link_check PASS 91/0, claims_hygiene PASS
49/0, die 42 Fixture-Tests gruen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Eine Falsifikations-Linse auf GENAU den Delta seit 48a0215 an release.yml.
Alle vier Funde treffen Code, den ich in derselben Runde geschrieben habe.

B1 — DER WAISE-MELDER WAR BLIND FUER SEINE EIGENEN FAELLE.
`orphan-draft-notice` hing nur an `publish-pypi`. Die Fehlerzweige, die der
neue fail-closed-Riegel in `publish-release` SELBST erzeugt — fehlende
Release-ID, leere oder unlesbare Antwort, gh-Fehler, gescheiterter PATCH —
enden mit liegengebliebenem Entwurf und feuerten NICHTS. Gemessen ueber alle
16 Kombinationen der Job-Ergebnisse: bei pypi=success und
publish-release=failure meldete niemand. Genau der Zustand, gegen den der Job
gebaut wurde. Der Riegel wurde strenger, sein Melder nicht mitgezogen.
  jetzt: needs + Bedingung um publish-release erweitert.

B2 — DIE ABHILFE EMPFAHL DEN WEG, DEN DIE DATEI ELF ZEILEN FRUEHER VERWIRFT.
`gh release delete $TAG --yes` loest ueber dieselbe FetchRelease-Wettlauf-
Funktion auf, deren Unbestimmtheit die Begruendung fuer den ganzen ID-Umbau
ist — und zwar mit Loeschwirkung. Im Doppel-Release-Fall, den die Datei selbst
als real bezeichnet, haette es das VEROEFFENTLICHTE Release treffen koennen.
Die eindeutige ID lag daneben und wurde nicht benutzt.
  jetzt: gh api -X DELETE repos/…/releases/$REL_ID.

B3 — MEIN defaults-KOMMENTAR LEHRTE MEHR, ALS DIE MESSUNG HERGAB.
`pipefail` rettet NICHT `echo "x=$(a | b)"` — der Rueckgabewert ist der von
`echo`, die Pipeline steckt als ARGUMENT in einer Substitution. Nachgemessen
unter der echten GitHub-Shell: die beiden Digest-Zeilen endeten weiter mit
rc=0 und schrieben einen LEEREN Wert; gefangen wurde das nur durch die
tee-Zeile danach, also durch Reihenfolge statt Absicherung.
  jetzt: Zuweisung + Leerpruefung + Zaehlpruefung. Die Leerpruefung ist der
  eigentliche Riegel — ein leerer Digest waere in publish-pypi gegen einen
  ebenfalls leeren Erwartungswert TRIVIAL WAHR durchgelaufen. Die
  Zaehlpruefung schliesst den Nachbarn (zwei Wheels -> mehrzeiliger Output
  ohne Delimiter).
  VERHALTENSPROBE unter `bash --noprofile --norc -eo pipefail`, vier Lagen:
    kein Wheel/kein sdist rc=1 · nur sdist rc=1 · Gutfall rc=0 · zwei Wheels rc=1
  GEGENPROBE mit der alten Fassung bei "nur sdist": rc=0, geschrieben
  `wheel=` (leer). Der Defekt ist von Hand reproduziert, nicht uebernommen.
  Der Kommentar sagt jetzt, was der Block NICHT deckt.

B4 — RUECKFALL IN DIE RISKANTE RICHTUNG. `${MAKE_LATEST:-true}` hiess bei
leerem Wert "als Latest setzen" — fuer ein Prerelease genau der Fehler vom
2026-07-05, gegen den relmeta gebaut wurde. Der Nachbar REL_ID bricht bei leer
ab; zwei leere Werte duerfen nicht gegenlaeufig behandelt werden.
  jetzt: leer -> BLOCK.

Gegengeprueft: YAML parst, 4/4 Output-Referenzen deklariert, 3/3
`needs.<job>.result` stehen in den needs desselben Jobs (eine undeklarierte
waere im Lauf ein leerer String, kein Fehler — also ein stiller Ausfall).

Was die Linse ausdruecklich NICHT widerlegen konnte, mit Messung: der
defaults-Block wirkt (github/docs workflow-syntax), `action-gh-release` setzt
`id` unbedingt auf dem Erfolgspfad (run.ts:99, Pin dereferenziert auf v3.0.2),
`-F draft=false -f make_latest=…` kodiert gegen einen lokalen Capture-Server
korrekt, die Drei-Zustands-Matrix ist in 9 von 9 Zweigen fail-closed, die
Waise-Meldung feuert in 16 von 16 Zellen nie falsch, und `sha256sum -c`
akzeptiert die neue SHA256SUMS-Form (Gegenprobe: die alte rc=1).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… massen nichts

Sechs Falsifikations-Linsen (E1-E6) auf `c9e94b3`, jede auf einen anderen
Gegenstand. E1 ist getrennt gelandet (ec2a573). Was hier steht, ist der Rest —
und der schwerste Teil trifft die Waechter, die ich in DERSELBEN Runde gebaut
habe, um andere Luecken zu schliessen.

## Zwei Waechter massen nichts, beide von zwei Linsen unabhaengig gefunden

DER SIGNATUR-VERFAELSCHER TRAF DIE FALSCHE ZEILE. `_mit_verfaelschter_signatur`
nahm per `reversed()` die LETZTE Zeile mit `— <origin> ` und nannte sie im
Docstring "die Signaturzeile des LOGS selbst (nicht der Zeugen)". Gemessen gibt
es ZWEI: Index 18 (Laenge 120, die Notensignatur) und Index 28 (Laenge 3272,
eine Zeugen-Cosignatur unter demselben Namen). Er traf die zweite; ihre
Verfaelschung laesst `log_ok` auf True. ENTSCHEIDPROBE einer Linse: mit einer
BYTE-IDENTISCHEN Kopie lief der Test ebenfalls gruen — er war gruen, egal ob
verfaelscht wurde oder nicht.
  jetzt: Auswahl nach GEMESSENER WIRKUNG statt nach Position oder Laenge. Der
  Helfer probiert jede Kandidatenzeile und liefert nur eine Kopie, bei der
  `log_ok` wirklich faellt; sonst None -> SKIP. Nachgemessen: er trifft jetzt
  Zeile 18 (log_ok=False, inclusion_ok=True), und eine byte-identische Kopie
  kaeme nicht mehr durch. Dazu ein tempdir-Leck geschlossen (je Versuch).

DER AST-WAECHTER WAR MIT DREI UMFORMUNGEN UMGEHBAR, und an zwei Stellen ist er
die einzige Deckung. Gemessen, je mit rohem ESC in stdout und GRUENEM Test:
  (a) `_safe_line = lambda s: s` lokal davor — der Name stimmt, die Funktion nicht
  (b) `{str(res['detail'])}{_safe_line('')}` — Alibi-Aufruf auf einer Konstante
  (c) ein UNBETEILIGTER f-String uebernimmt die Deckung, weil die Marke ein WORT
      war und keine Stelle
  jetzt: Bindung an die STELLE. Die Einsetzung direkt hinter der Beschriftung
  MUSS selbst der `_safe_line`-Aufruf sein, sein Argument darf keine Konstante
  sein, und der Name `_safe_line` darf nirgends neu gebunden werden.
  PROBE gegen genau die drei Umformungen: alle drei jetzt ROT, unveraendert gruen.

## Eine Behauptung war zu weit, eine Korrektur ueberschoss

DIE DREI URSACHEN. Der Befund sagte pauschal, mit gesetztem Flag seien alle drei
byte-identisch. Gemessen gilt das NUR, wenn in allen drei Laeufen derselbe
FREMDE Origin gepinnt wird — dann ist `log_ok` schon aus dem Origin-Grund False.
Pinnt der Pruefer den Origin, dem er traut (der dokumentierte Gebrauch), ist der
fremde Origin sehr wohl maschinell lesbar; ununterscheidbar bleiben falscher
log-vkey und verfaelschte Signatur. Zwei-zu-eins, nicht drei-zu-eins.
  Erste Fassung: "das Feld trennt alle drei" — falsch.
  Zweite Fassung: "es trennt keine" — ebenfalls falsch, in die andere Richtung.
  Beide auf einer Konstruktion gemessen und ueber eine andere berichtet.
  jetzt: zwei Waechter, einer je Haelfte. Der eine haelt den ununterscheidbaren
  Teil fest (und wird rot, wenn die Luecke je zugeht), der andere den lesbaren.

## Vier weitere Luecken, jede mit Ruecknahme-Probe geschlossen

NFC/NFD (kanonische Normalform). Der Vollbreiten-Kandidat faengt NFKC, aber
NICHT NFC — gemessen `NFC(vollbreite) != Origin`. Ein Meta-Test hatte die
NFC-Lockerung als von KEINEM der 2088 Tests gefangen gemessen. Der
Fixture-Origin ist ASCII und kann keinen NFC-Kandidaten hergeben, also baut die
neue Klasse ihren EIGENEN Checkpoint mit der ausgelieferten API und einem
Wegwerf-Schluessel (keine zweite Fixture, die eingefrorene bleibt unberuehrt).
  PROBE: mit gepflanzter NFC-Normalisierung 2 rot, ohne 22 gruen.

DER LEERSTRING IM TEXTPFAD. `cli.py:988` von `is not None` auf falsy gelockert
liess ALLE 2086 Tests gruen: der Mutant aendert weder rc noch JSON, er
verschweigt nur, dass eine Erwartung GESETZT und verletzt wurde. Ein Pruefer mit
leerer Variable saehe ein nacktes `[FAIL]` und suchte den Fehler beim Artefakt.
  PROBE: mit L9 gepflanzt genau der neue Test rot, Basis 24 gruen.

DIE BIBLIOTHEKS-EBENE war ungedeckt. `test_expected_origin_is_enforced` prueft
nur einen VOELLIG FREMDEN Wert — und gegen einen fremden Wert verhaelt sich jede
Lockerung wie ein exakter Vergleich. Wer `verify_tlog_proof` direkt aufruft (die
dokumentierte oeffentliche API), war nur gegen Totalentfernung geschuetzt; die
ganze Haertung lebte auf dem CLI-Pfad. Sieben Beinahe-Treffer ergaenzt, dieselben
Formen wie im CLI-Korpus — eine Eigenschaft, zwei Ebenen.

DER TEST, DESSEN NAME EINE EIGENSCHAFT TRAEGT, DIE ER NICHT PRUEFT.
`test_ohne_flag_ist_das_feld_None_nicht_leerstring` pruefte nur das JSON-Echo und
blieb bei der absent-vs-leer-Pflanzung gruen. Jetzt steht die Eigenschaft dort,
wo ihr Name steht.

## Drei Mutations-Operatoren, damit das Korpus nicht still schrumpfen kann

Fuenf Klassen hingen an EINER Testdatei; faellt sie weg, meldet das Tor gruen
statt SURVIVED. Fuer NFC, absent-vs-leer und die `_safe_line`-Umwicklung gab es
keinen Operator.
  84 -> 87 Operatoren. GEGENPROBE: alle 87 treffen ihr Muster GENAU EINMAL
  (nicht 0 = stale, nicht 2 = mutiert mehr als gemeint).
  TOETUNGS-PROBE selbst gefahren, nicht uebernommen: NFC 0->2 rot, absent-vs-leer
  0->1, Steuerzeichen 0->2. Ruecknahme je vollstaendig.

## Zahlen in der Akte, alle einzeln nachgerechnet und korrigiert

  "21 Dateien / 15 / 5"  -> stimmt bei 039ac5d; der Commit, der sie EINFUEGT
                           (c9e94b3), macht 22/16/6. Eine Messung mit `HEAD` im
                           Befehlstext ist selbstwidersprechend: das Aufschreiben
                           bewegt HEAD. Jetzt beide Paare mit ihrem Pin.
  "diffing all 666 members" -> gemessen 743 Mitglieder / 648 Dateien. 666
                           reproduziert in keiner Zaehlweise.
  "six lines above"      -> 29 Zeilen, andere Tabelle.
  RELEASE.md-Rezept      -> die von mir ergaenzte Zeile funktionierte mit dem
                           Befehl darueber NICHT: `pip download` legt nur das Rad
                           ab, SHA256SUMS listet beide. `sha256sum -c` meldete
                           "No such file or directory" — woertlich das Symptom,
                           das derselbe Absatz als behoben fuehrt.
                           GEMESSEN, welche Form traegt: `--ignore-missing`, und
                           BEIDE Gegenproben halten (falscher Digest rc=1, leeres
                           Verzeichnis rc=1 "no file was verified").

## Zwei Befunde vorgelegt statt still gefixt (beide auf main)

FINDING_erwartungsvergleich_klasse.md — die Klasse, die dieses Release an EINER
Instanz geschlossen hat, hat SECHS weitere Mitglieder: kbjwt.py:211 (audience,
RFC 9901 Replay), :214 (nonce), statuslist.py:162, evalclaim.py:334,
intoto.py:302, policy.py:970. Jeder einzeln auf startswith gelockert, volle Suite
je Pflanzung: alle sechs blieben auf Baseline. Ausfuehrbar belegt fuer die ersten
zwei. Die Wurzel ist woertlich dieselbe wie die gefixte: test_kbjwt.py:93 prueft
gegen einen VOELLIG FREMDEN Wert.

FINDING_nachbarflaeche_ohne_origin_bindung.md — `verify --trusted-checkpoint`
nimmt einen Checkpoint als authentifizierte Quelle fuer root UND tree_size, von
JEDEM Log, und es gibt KEIN Flag, das den Origin bindet. Ausgeloest, nicht
erschlossen: dieselbe root, derselbe Schluessel, zwei Origins -> byte-gleiches
Verdikt, beide ROOT-AUTHENTICITY PASS. Das ist genau die Bedrohung, die dieses
Release auf `verify-proof` schliesst. Der schwerste offene Punkt der Runde.

Beide sind Altbefunde ausserhalb des Release-Deltas (`git diff v3.7.0..HEAD --
src/` ist cli.py und __init__.py) und werden nach der stehenden Regel GEMELDET.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…eine zaehlt

Pflicht-Gegenlesung des Waechter-Deltas. Vier Befunde zurueck, drei davon habe
ich gemessen statt geglaubt und dabei widerlegt; einer traegt und ist behoben.

## Der Befund, der traegt: ein SKIP verdeckt, dass nichts gemessen wurde

`test_falscher_schluessel_und_verfaelschte_signatur_bleiben_ununterscheidbar`
rief `self.skipTest(...)`, wenn keine Signaturzeile der Fixture `log_ok` kippt.
Die Klasse ist bereits `skipUnless(_PROOF.is_file())` — ist die Fixture also DA
und wirkt trotzdem keine Zeile, hat sich ihr FORMAT geaendert und der Test misst
nichts mehr. Ein SKIP ist dort die stille Variante von "nichts gefunden = alles
gut" und geht in einer Suite mit 2097 Tests unter.
  jetzt DREI Zustaende: Fixture fehlt -> SKIP (die Klasse) · Fixture da, keine
  Zeile wirkt -> FAIL · Zeile wirkt -> messen.
  GEMESSEN in einer Wegwerfkopie mit unpassend gemachtem Praefix:
    vorher wie nachher unveraendert 1 passed · mit dem Fix 1 FAILED.

## Drei Befunde, von der Messung widerlegt

ALIAS UMGEHT DEN AST-WAECHTER — nein, umgekehrt. Der Waechter verlangt den
Namen `_safe_line` an der Stelle hinter der Beschriftung; ein Alias laesst genau
das FEHLEN, also wird `fehlend` nicht leer und der Test ROT. Er faellt
fail-closed, das ist Bauart, nicht Zufall. Drei Formen einzeln gefahren:
    safe = _safe_line (Assign)                 -> ROT
    from ._sl import _safe_line as sl          -> ROT
    str.format statt f-String, Umwicklung weg  -> ROT
  Kontrolle unveraendert gruen.

NFC-OPERATOR NICHT TOEDLICH BEI ASCII-ONLY — beruht darauf, dass ich die
Testklasse aus der Vorlage gekuerzt hatte. `KanonischeNormalformZaehltAlsUnter-
schied` baut einen EIGENEN Checkpoint mit nicht-ASCII-Origin. Toetungs-Probe
ueber die echte Mechanik: 0 -> 2 rot.

ABSENT-COLLAPSES-OPERATOR NICHT TOEDLICH — dieselbe Toetungs-Probe: 0 -> 1 rot.
Der Einwand ist gut begruendet (falsy trifft auch 0 und False), aber die Frage
war, ob der Operator TOETET, und das ist gemessen.

Die Lehre aus der Runde ist nicht "die Gegenlesung lag daneben" — sie hat den
einen Befund gefunden, den fuenf Linsen davor uebersehen hatten. Sie ist, dass
ein gekuerzter Pruefgegenstand Fehlurteile erzeugt: zwei der drei widerlegten
Befunde folgen direkt aus dem, was ich weggelassen habe.

Gemessen, Abschluss: 1 failed / 2097 passed / 9 skipped / 50 subtests · ruff
clean · doc_link 91/0 · claims 49/0 · check_version OK · pre_tag_audit rc=1
(muss rot sein, es gibt kein Verdikt).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Das Pre-Tag-Tor ist mit --strict der ERSTE Schritt des Release-Workflows und
damit der letzte blockierende Riegel zwischen Tag und PyPI. Eine Linse hat
gemessen, dass es mit dem, wonach es benannt ist, in KEINER Richtung
korreliert. Damit haengt die Frage "kann dieses Release ehrlich geschnitten
werden" daran, ob das Tor durch eine wahre Aussage und NUR durch eine wahre
Aussage erfuellbar ist.

BEIDE Fassungen nebeneinander geladen, gegen denselben Wegwerf-Baum, ein
Record je Vektor. GEGENPROBE ZUERST, weil ein Tor, das immer MISSING sagt, auf
der Falsch-Gruen-Achse perfekt aussaehe: bei leerem Record melden BEIDE MISSING.

  A  zwoelf Saetze, die einen NICHT gelaufenen Audit behaupten
     (mehrere Sprachen; Kommentar, Code-Zaun, Front-Matter, Durchstreichung,
      Frage, URL)                       heute 0/12 richtig · #139 12/12
  B  vier ehrliche Attestierungen in unserer Hausform
                                        heute 0/4  · #139 0/4
  C  die kanonische Attestierungszeile   beide richtig
  D  dieselbe Zeile mit der Version eines FRUEHEREN Release
                                        heute akzeptiert (falsch) · #139 abgelehnt

KLASSE A ist der Release-Blocker, und #139 schliesst ihn vollstaendig. Die
heutige Marker/Negations-Paarung ist eine AUFZAEHLUNG: sie listet, wie ein Satz
etwas verneinen kann, und jede nicht gelistete Form liest sich als Behauptung.
Zwoelf von zwoelf kamen durch. #139 dreht die Polaritaet — EINE geschlossene
Vollzeilen-Form, und in einer geschlossenen Form kann keine Verneinung wohnen,
also muss kein Vokabular aufgezaehlt werden.

KLASSE D hatte niemand gemeldet. Bis #139 war der versionsbenannte Ordner der
einzige Anker, also attestierte ein aus einem frueheren Release kopierter Record
das neue, indem er im richtigen Verzeichnis lag.

KLASSE B sieht unveraendert aus und ist KEIN Defekt: in #139 ist Prosa
ausdruecklich praesentational und kann das Verdikt in keiner Richtung bewegen.
Die Kategorie loest sich auf, statt behoben zu werden. Unter dem heutigen Tor
werden dieselben vier Saetze aus dem GEGENTEILIGEN Grund abgelehnt — jeder
enthaelt ein Wort, das die Negationsliste als Verneinung liest. Ein
wahrheitsgemaesser Record in unserem eigenen Stil wird abgelehnt, waehrend
zwoelf unwahre durchkommen.

WAS DAS BELEGT: die bereits gewaehlte Merge-Reihenfolge ist tragend, nicht
kosmetisch. Ohne #139 heisst "das Tor erfuellen", einen Satz zu schreiben, der
ein Markerwort traegt und eine Liste anderer vermeidet — also um eine Pruefung
herumzuschreiben statt eine Tatsache zu attestieren.

WAS ES NICHT BELEGT: dass ein Audit gelaufen ist. #139 nennt seine eigene Grenze
klar — die kanonische Zeile ist provenance-FOERMIG, nicht Provenance.

Die Datei traegt bewusst KEIN Markerwort: die Vektoren wuerden das heutige Tor
kippen, und sie im Release-Record auszuschreiben hiesse, den berichteten Defekt
darin zu reproduzieren. Gegengeprueft: 0 Marker, Tor weiter rc=1, doc_link 91/0,
claims 49/0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e fand sofort drei mehr

Eine Gegenlesung meldete, dass "drei Werte kommen aus einer Datei, die der
Pruefer nicht geschrieben hat" sich als VOLLSTAENDIG liest und es nicht ist. Der
Einwand traf, und die richtige Antwort war nicht, eine vierte Stelle zu
umwickeln, sondern den Waechter von der Aufzaehlung auf die Regel umzustellen.

## Der Waechter prueft jetzt die Eigenschaft, nicht die Marken

VORHER: vier Marken (`log-signature`, `expected`, `sample-opening`,
`enclave-attestation`) mussten je durch `_safe_line` gehen. Eine Aufzaehlung
faengt nicht, woran niemand gedacht hat.

JETZT zusaetzlich die UMKEHRUNG: JEDE f-String-Einsetzung, die ein `detail`-
oder `origin`-Feld liest, geht durch `_safe_line` ODER traegt `!r` — ausser den
NAMENTLICH und mit Grund ausgenommenen. Eine neue Zeile bindet automatisch.

`!r` steht dort als GLEICHWERTIGE Verteidigung, nicht als Ausnahme: die
Eigenschaft ist "der Wert kann keine Zeile faelschen", und `repr()` leistet das.
GEMESSEN: ESC -> `\x1b`, Zeilenumbruch -> `\n`, ZWSP -> `​`, kein rohes
Steuerzeichen bleibt. Wer hier nur `_safe_line` zaehlte, maesse den Mechanismus
statt die Sache — derselbe Fehler, den dieser Waechter schon einmal gemacht hat.

## Die Umkehrung fand SOFORT drei weitere Stellen

Beim ersten Lauf rot, alle drei beschriftete stdout-Zeilen mit einem Wert, den
der Aussteller eines Beweises frei waehlt:
  cli.py:1351  [anchor verify-pack] … — {out['detail']}
  cli.py:1251  [anchor upgrade] NOT UPGRADED (…) — {msg['detail']}
  cli.py:844   recomputed root  (not computable: {roots['detail']})
Die Quellen sind nachgemessen exception-abgeleitet: anchors_ots.py:87,
anchors_chia.py:163, anchors_rfc3161.py:81 bauen `detail` aus `{exc}`, und
bundle.py:748 gibt `str(exc)` zurueck. Alle drei umwickelt.

Zwei weitere Treffer der Umkehrung sind KEINE Defekte und stehen jetzt mit
ihrem Grund da statt durch Auslassung: `checkpoint origin {…!r}` (andere,
gemessene Verteidigung) und `prereg`/`evalcard` (Literale, nachgemessen in
prereg.py:78-94 und evalcard.py:78-96). Die `ERROR:`-Zeilen gehen nach stderr.

## Ruecknahme-Probe fuer den KLASSEN-Fix selbst

  Kontrolle unveraendert                                  GRUEN
  anchor verify-pack entwickelt (auf KEINER Liste)        ROT
  eine voellig NEUE beschriftete Zeile mit rohem detail   ROT

Der zweite Fall ist der Punkt: eine Zeile, die es beim Schreiben des Waechters
nicht gab, bindet trotzdem. Das ist der Unterschied zwischen Instanz und Klasse,
und er ist hier gemessen statt behauptet.

Gemessen, Abschluss: 1 failed / 2097 passed / 9 skipped / 50 subtests · ruff
clean · doc_link 91/0 · claims 49/0 · check_version OK · pre_tag_audit rc=1
(muss rot sein) · 87 Mutations-Operatoren, alle mit genau einem Treffer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…— und eine Zahl, die einen Fall auslaesst

Eine Gegenlesung monierte, dass die Delta-Liste des Release-Abschnitts bei
`src/` aufhoert. `MANIFEST.in` graftet aber `scripts` und `docs/readiness_pack`
in den sdist — beides erreicht also jeden, der aus der Quelle installiert.

DER SELBSTBELEG DES READINESS-PACKS WURDE NEU SIGNIERT (9b8a998).
`readiness_pack.pub.b64` wechselt von aQDV4Vkc… auf GB+LMY2k…, mit neuer
Signatur und neuer Wurzel. In einem Release-Diff sieht das aus wie eine
Schluesselrotation und ist keine: der Beleg ist advisory und wird bei jeder
Neuerzeugung mit einem EPHEMEREN Schluessel signiert. Das steht in
`scripts/readiness_pack_manifest.py` — und nirgends dort, wo ein Leser
nachschauen wuerde. Jetzt im CHANGELOG, damit der naechste Blick keinen
Fehlalarm ausloest.

`scripts/mutation_check.py` BRAUCHT JETZT EIN GIT-ARBEITSVERZEICHNIS.
Es ruft `git ls-files` und bricht mit einer Meldung ab, wenn das scheitert
(:497-500, aus e34e05e); die 3.7.0-Fassung kannte diese Abhaengigkeit nicht.
Aus einem entpackten sdist gibt es kein Repo, die ausgelieferte Kopie ist dort
also nicht lauffaehig. EHRLICHE GRENZE, im CHANGELOG mitgeschrieben: das ist
aus dem Quelltext und aus MANIFEST.in gelesen, NICHT Ende-zu-Ende reproduziert —
ein Lauf startet den echten Mehrstunden-Job. (Mein erster Versuch tat genau das
und wurde gestoppt.) Drei weitere ausgelieferte Dateien sind neu dabei:
mutant_signature_guard.py, install_git_hooks.sh, git-hooks/pre-commit.

UND EINE ZAHL IN DER PRAEREGISTRIERUNG: Abschnitt 11 sagt "VIER Verstoesse" der
alten Bindemenge gegen ihre eigene Regel. Es sind FUENF —
`docs/adr/renewal_policy.example.json` wird von MANIFEST.in:27 ausgeliefert und
faellt unter das pauschale `docs/`-Ausschliessen, nach genau demselben Argument
wie `docs/readiness_pack/`. Die Ausschlussliste des Abschnitts nimmt den Pfad
BEREITS aus; falsch war nur die Zaehlung darueber. Dieselbe Klasse, eine Ebene
kleiner: eine Aufzaehlung, die ihre eigene Regel richtig anwendet und beim
Nachzaehlen einen Fall auslaesst.

Gegengeprueft: 0 Markerworte in den Akten-Dateien, pre_tag_audit rc=1, doc_link
91/0, claims 49/0, check_version OK, die Waechter-Umkehrung 24 gruen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…in Feld sagt warum

Selbst nachgemessen (nicht von einer Linse uebernommen), gegen die eingefrorene
Fixture, mit lebender Gegenprobe am Ende:

  Default (--threshold weggelassen, keine Zeugenschluessel)
      ok=true  witnesses_ok=TRUE  bestaetigende Zeugen=0  exit 0
  ein Zeugenschluessel               ok=true  witnesses_ok=true  bestaetigend=1
  --threshold 4, fuenf Schluessel    ok=true  witnesses_ok=true  bestaetigend=5
  --threshold 9 (unerfuellbar)       ok=FALSE witnesses_ok=FALSE exit 1   <- die Kontrolle kippt

`threshold` kommt im --json GAR NICHT vor. Die neun Schluessel sind ok, log_ok,
witnesses_ok, inclusion_ok, origin, tree_size, index, witnesses, expected_origin.

FOLGE: `witness_quorum` gibt `len(bestaetigt) >= threshold` zurueck, und bei
threshold=0 ist das bedingungslos wahr. Ein Programm, das `witnesses_ok` liest,
sieht dasselbe `true` fuer "ein verlangtes Quorum wurde erreicht" und fuer "es
wurde nie eines verlangt" — und kein Feld der Ausgabe trennt die beiden. Die
Zeugen zu zaehlen hilft nicht: null bestaetigende ist unter threshold=0 ein
legitimer Zustand, und dieselbe Null unter einer verlangten Schranke haette
witnesses_ok auf false gesetzt — nur ist die Schranke da schon nicht mehr da.

DAS VERDIKT IST KORREKT. threshold=0 heisst kein Zeugen-Erfordernis, und ok=true
auf einem Beweis mit gueltiger Log-Signatur und Inklusion ist die richtige
Antwort. Was fehlt, ist nicht eine Verteidigung, sondern die LESBARKEIT der
Antwort auf dem Maschinenpfad — genau die Form der Luecke, die 3.8.0 fuer den
Origin geschlossen hat, ein Feld weiter. Der Textpfad ist besser dran: er nennt
die Schranke ("threshold T"). Wer auf --json automatisiert, bekommt weniger als
wer das Terminal liest.

NICHT IN 3.8.0 GEAENDERT: `threshold` ins JSON zu legen waere die DRITTE
Aenderung der Ausgabeform in einem Release, dessen CHANGELOG genau deswegen
schon zweimal korrigiert werden musste. verify-proof ist ausserhalb des
Release-Deltas — also ein main-Befund unter der Regel, der diese Runde folgt.

EHRLICHE GRENZE, in der Datei mitgeschrieben: ob ein Konsument wirklich in die
Irre geht, ist NICHT gemessen — das haengt daran, ob jemand auf witnesses_ok
automatisiert, ohne --threshold selbst zu setzen, und das sieht keine
Repo-Messung. Gemessen ist, dass die Ausgabe es ihm nicht erlaubt.

Gegengeprueft: 0 Markerworte, pre_tag_audit rc=1, doc_link 91/0, claims 49/0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…er ohne Index

Die 3.7.0-Akte war EINE Datei. Diese ist auf acht gewachsen, weil der Lauf
immer wieder etwas ueber seine eigenen Instrumente fand. Wer das Verzeichnis
oeffnet, hatte bis jetzt keinen Einstieg: keine Reihenfolge, keine Aussage
darueber, was offen ist, und keine Stelle, an der steht, dass es KEIN Verdikt
gibt.

Der Index sagt das zuerst: das Tor ist rot und muss es sein, nichts hier
attestiert einen abgeschlossenen Lauf. Danach drei Ebenen — wo man anfaengt,
welche fuenf Befunde offen sind und was jeder braucht, und eine Lesereihenfolge
fuer einen Pruefer.

DER INDEX TRAEGT ABSICHTLICH KEIN MARKERWORT, und der Grund steht in ihm drin:
das Tor auf diesem Zweig erteilt die Freigabe fuer JEDE nicht-negierte
Markerzeile in JEDER .md hier. Ein Index, der das Vokabular benennt, wuerde das
Release attestieren, indem er es beschreibt. Dass genau das moeglich ist, ist
selbst einer der Befunde darunter.

Er nennt auch die ehrliche Zusammenfassung der Runde, weil sie sonst zwischen
acht Dateien verschwindet: der Pruefgegenstand hielt jeder Messung stand, die
INSTRUMENTE nicht. Zwei Waechter dieser Runde massen nachweislich nichts, fuenf
Zahlen in den Akten waren falsch — und die Klasse hinter fast allem hat eine
Form: eine Zahl ueber eine Population gemessen und ueber eine andere berichtet.

Gegengeprueft, mit Gegenprobe gegen tote Verweise: 0 Markerworte, Tor weiter
rc=1, alle 8 Nachbardateien im Index genannt (keine fehlt), doc_link 91/0,
claims 49/0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ndelt, im Index selbst

Der Index, den ich als Einstieg geschrieben habe, sagte: "All five are on main
and predate this release." GEMESSEN, je Befund ueber die Frage, ob sein
Gegenstand bei v3.7.0 existiert:

  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 -> DIESES Release
  quorum (witnesses_ok)                              v3.7.0 ja   -> Altbefund
  never-raise population (_MODULES)                  v3.7.0 ja   -> Altbefund

VIER von fuenf. Das ist zum vierten Mal in dieser Sitzung dieselbe Klasse — ein
Sammelbegriff, der fuer keinen der Einzelfaelle geprueft wurde — und diesmal in
dem Dokument, das ein Pruefer zuerst oeffnet.

DER UNTERSCHIED IST KEINE BUCHHALTUNG. Fuer die vier gilt die Regel dieses
Laufs: ein main-Befund wird GEMELDET, nicht in ein Release gefaltet, das ihn
nicht verursacht hat. Der dritte ist anders: `expected_origin` im JSON ist HIER
neu, die zu weite Behauptung darueber wurde HIER gemacht, und ihre Korrektur
gehoert damit in dieses Release und nicht in ein spaeteres. Genau so ist sie
auch behandelt worden — im CHANGELOG und im Befund korrigiert, mit zwei
Waechtern, die den gemessenen Stand halten.

Der Index sagt das jetzt mit der Messung daneben, statt mit einem Wort, das
vier Faelle richtig und einen falsch beschreibt.

Gegengeprueft: 0 Markerworte, Tor weiter rc=1, doc_link 91/0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rtefakt

Selbst nachgemessen, Positivkontrolle zuerst (guter Lauf rc=0, ok=true), dann
vier Fehlerursachen mit sha256 ueber die volle stdout:

  leere Beweisdatei                        rc=1  6f5177382070a08a
  --threshold -1  (Tippfehler des Pruefers) rc=1  6f5177382070a08a
  kaputter --log-vkey (Tippfehler)          rc=1  6f5177382070a08a
  Muell-Bytes                               rc=2  0e35e243f9e23c02  <- trennbar

DREI von vier sind byte-identisch. DIE ZAHL IST KLEINER ALS BERICHTET: die
Linse, die das fand, sagte vier; nachgemessen trennt sich der Muell-Fall (eigener
Exit-Code, eigenes error-Feld). Der Befund ist damit schmaler — und in einer
Hinsicht schlimmer, als die Zahl nahelegt.

WARUM SCHLIMMER: von den drei kollabierenden sind ZWEI Fehler in der
Kommandozeile des PRUEFERS, keine Eigenschaft des geprueften Artefakts. Die
Ausgabe fuer "du hast einen negativen Threshold uebergeben" und "dein
Verifizierer-Schluessel ist kaputt" ist nicht von "diese Datei ist kein Beweis"
zu unterscheiden. Der Pruefer liest ein Urteil ueber das Artefakt und faengt an,
das Artefakt zu untersuchen, waehrend der Fehler in seiner eigenen Zeile steht.
Der Textpfad macht es konkret: `--threshold -1` druckt `[FAIL] log-signature:
None` — geprueft wurde nie etwas (der Threshold-Waechter feuert vor dem Parsen),
aber die Zeile nennt die Log-Signatur, also genau das, was der Bediener jetzt
nachsehen wird.

ANDERE KLASSE ALS DER DREI-URSACHEN-BEFUND, und der Unterschied traegt: dort
kollidieren drei echte VERIFIKATIONSERGEBNISSE (alle drei heissen wirklich
"dieser Beweis verifiziert nicht"). Hier kollidiert NICHT MESSBAR mit GEMESSENEM
NEIN — einer der beiden Zustaende ist ueberhaupt kein Urteil.

Die Information existiert und geht eine Schicht vor der Ausgabe verloren: die
Bibliothek liefert je Ursache ein praezises `detail`, und `cli.py` zaehlt die zu
uebernehmenden Schluessel hart auf, ohne `detail`.

NICHT GEAENDERT: verify-proofs Fehlerpfad liegt ausserhalb des Release-Deltas
(--threshold und _tlog_failclosed existieren bei v3.7.0, nachgemessen), also ein
main-Befund unter der Regel dieser Runde.

INDEX MITGEZOGEN, und dabei eine dritte Fassung derselben Zahl: der Index sagte
"vier von fuenf" und wurde durch den sechsten Befund still falsch. Jetzt "fuenf
von sechs" — mit Gegenprobe, die die Zahl gegen die tatsaechliche Dateimenge und
gegen die Altbefund-Zeilen im Codeblock rechnet (6 Dateien, 6 Tabellenzeilen,
5 Altbefunde, alles deckungsgleich). Diesmal vor der Veroeffentlichung gefangen,
beim ersten Mal nicht.

Gegengeprueft: 0 Markerworte in beiden Dateien, pre_tag_audit rc=1, doc_link
91/0, claims 49/0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Die Zahl "N von M Befunden sind Altbefunde" ist mir in dieser Runde ZWEIMAL
weggerutscht, beide Male im Dokument, das ein Pruefer zuerst oeffnet:

  1. "all five are on main"   -> gemessen VIER von fuenf
  2. "FOUR of the five"       -> korrekt, bis ein sechster Befund dazukam

Beide Male verwischte sie eine Zustaendigkeit: ein Altbefund wird GEMELDET, ein
Befund dieses Release wird REPARIERT. Ein Sammelbegriff, der beides zusammenzieht,
nimmt dem Leser genau die Unterscheidung.

WARUM EIN TEST UND KEIN VORSATZ. Diese Runde hat die Frage beantwortet: sieben
Faelle derselben Klasse, davon null vor dem Schreiben von Hand gefangen; die
Treffer gehen auf Automatismen. Ein Vorsatz hat hier nachweislich nicht getragen.

DIE REGEL, NICHT DER FALL: geprueft wird JEDE `audit_artifacts/<token>/00_INDEX.md`,
nicht die eine von 3.8.0. Vier Zusicherungen:
  - `N of the M`: M == Zahl der FINDING-Dateien im selben Verzeichnis
  - `N of the M`: N == Zahl der als Altbefund markierten Nachweis-Zeilen
  - jede FINDING-Datei steht in der Tabelle (der Nachbar-Fehler: nicht die Summe
    stimmt nicht, sondern ein Element fehlt — beides sieht nach Vollstaendigkeit aus)
  - der Index traegt KEINE nicht-negierte Markerzeile: ein Index, der das Vokabular
    des Tors benennt, attestiert das Release, indem er es beschreibt. Geprueft gegen
    den ECHTEN Detektor des Tors, nicht gegen eine nachgebaute Kopie — eine zweite
    Messstelle fuer dieselbe Groesse waere die naechste Drift.

Eine Akte OHNE Index ist kein Fehler (aeltere Releases hatten eine einzige Datei);
ein Index mit falscher Zahl schon. Und eine Gegenprobe des Messaufbaus ist selbst
eine Zusicherung: findet das Muster keinen einzigen Index, faellt der Test, statt
ein leeres Verzeichnis wie ein makelloses Ergebnis aussehen zu lassen.

RUECKNAHME-PROBE gegen die drei Fehler, die wirklich passiert sind:
  Kontrolle unveraendert                 5 passed, 4 subtests
  veraltete Zahl                         2 FAILED   <- Gesamt- UND Teilzahl
  Befund fehlt in der Tabelle            1 FAILED
  Markerwort im Index                    1 FAILED
  nach allen Ruecknahmen                 5 passed, 4 subtests  (Basis exakt zurueck)

Gemessen, Abschluss: 1 failed / 2102 passed / 9 skipped / 54 subtests · ruff clean
· doc_link 91/0 · claims 49/0 · check_version OK · test_manifest 2112 gesammelt
(Boden 1750) · pre_tag_audit rc=1 (muss rot sein).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…icht nur in der Akte

GEMESSEN: `MANIFEST.in:32` prunt `audit_artifacts/` aus dem sdist. Wer 3.8.0
installiert oder die Release-Seite liest, sah die Korrekturen — und von den
sechs offenen Befunden GENAU EINEN, beilaeufig in einem Nebensatz. Die
Vollstaendigkeit der Akte nuetzt niemandem, der sie nicht bekommt.

Neuer Abschnitt `### Known limitations` mit allen sechs, jeweils in der Form,
die einen NUTZER angeht statt uns:
  - ein Tippfehler des Pruefers liest sich wie ein kaputtes Artefakt
    (Abhilfe: Exit-Code pruefen, eigene Argumente validieren)
  - `witnesses_ok: true` heisst nicht, dass ein Quorum erreicht wurde
    (Abhilfe: --threshold explizit setzen und mitschreiben)
  - `verify --trusted-checkpoint` hat keine Origin-Bindung
    (keine Abhilfe im Werkzeug — Provenance ausserhalb pinnen)
  - der --json-Pfad trennt falschen Schluessel nicht von verfaelschter Signatur
  - sechs Vergleichsflaechen ohne Beinahe-Treffer-Beleg
  - die never-raise-Eigenschaft laeuft ueber eine handgepflegte Modulliste

Der Abschnitt sagt zwei Dinge ausdruecklich, weil sie sonst falsch gelesen
werden: FUENF der sechs sind aelter als 3.8.0 und werden gemeldet statt
eingefaltet — ein Release soll keine Defekte still aufsaugen, die es nicht
verursacht hat; der sechste betrifft ein Feld, das DIESES Release hinzugefuegt
hat, und ist hier korrigiert. Und KEINER macht ein Verdikt falsch: es geht
durchweg darum, was die Ausgabe einen Pruefer UNTERSCHEIDEN laesst, oder was
unser eigener Beleg bemerken wuerde, wenn man ihn entfernte.

Gegengeprueft: 6 Aufzaehlungspunkte gegen 6 Befund-Dateien (deckungsgleich),
0 Markerworte im 3.8.0-Abschnitt, pre_tag_audit weiter rc=1, doc_link 91/0,
claims 49/0, check_version OK.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…stiert

Der neue Known-limitations-Abschnitt sagt ausdruecklich, dass die vollen Akten
unter `audit_artifacts/` liegen und `MANIFEST.in:32` sie aus dem sdist prunt.
EIN aelterer Verweis im selben Abschnitt tat das nicht: er nannte den Pfad zum
Befund, als koennte ein Leser ihn oeffnen.

GEMESSEN: fuenf solcher Verweise im ganzen CHANGELOG, zwei im 3.8.0-Abschnitt.
Die Form ist also aelter als diese Runde — was sie nicht besser macht fuer den,
der aus dem sdist liest.

Der Verweis sagt jetzt, was verfuegbar ist und was nicht: die Akte lebt im Repo
und ist NICHT Teil des sdist, die zwei Waechter dagegen SIND ausgeliefert
(`MANIFEST.in:21 graft tests`). Das ist die nuetzlichere Auskunft — wer die
Behauptung nachpruefen will, braucht die Waechter, nicht die Prosa.

Gegengeprueft statt behauptet: `graft tests` steht in MANIFEST.in, die
Waechter-Datei existiert, und beide genannten Testfunktionen sind darin.
Dazu: 0 Markerworte im 3.8.0-Abschnitt, pre_tag_audit rc=1, doc_link 91/0,
check_version OK.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…enannt

Die Pflicht-Gegenlesung hat REJECT gegeben, mit vier Befunden gegen die zwei
Waechter, die ich in dieser Runde gebaut habe. Erst die Erreichbarkeit gemessen,
dann behoben — keiner ist HEUTE realisiert, und genau deshalb sind sie das
Naechste, was durchrutscht:

  K1 print() mit Konkatenation statt f-String   in cli.py 0x  -> latent
  K2 res.get('detail') statt res['detail']       in cli.py 0x  -> latent
  K4 Index behauptet Teilzahl, hat 0 Markierungen             -> latent
  K5 zwei Zahlenaussagen, nur die erste geprueft              -> latent

K1 — DER WAECHTER SIEHT NUR f-STRINGS, und das ist eine Annahme, keine
Eigenschaft. `print("log-signature: " + res['origin'])` traegt keinen JoinedStr
und ginge unbemerkt durch. Statt den Sonderfall zu behandeln wird die ANNAHME
festgehalten: keine print()-Zeile benutzt Konkatenation, %-Formatierung oder
.format(). Faellt diese Zusicherung, ist nicht sie das Problem, sondern die neue
Ausgabeform — die dann selbst eine Regel braucht.

K2 — die Regex sah nur `x['detail']`. `.get('detail')` liest denselben Wert und
ist eine Zeichenaenderung entfernt. Beide Formen jetzt.

K4 — DERSELBE STILLE SKIP, den ich in dieser Runde schon einmal repariert habe.
`if markiert == 0: continue` hiess: ein Index, der "three of the ten" behauptet
und KEINE Zeile als Altbefund ausweist, wurde gar nicht geprueft. Jetzt drei
Zustaende statt zwei — keine Aussage: nichts zu pruefen · Aussage nennt 0 und es
gibt 0: stimmt · Aussage nennt >0 und es gibt 0: FEHLER.

K5 — `.search` nahm die erste Aussage. Bei zwei Saetzen blieb der zweite
ungeprueft. Jetzt `finditer` ueber alle.

K3 (`!a` neben `!r` akzeptieren) NICHT umgesetzt, und das ist Absicht: nur `!r`
zu akzeptieren ist STRENGER. Eine Ausgabeform, die der Waechter nicht kennt,
soll auffallen, nicht durchgehen.

RUECKNAHME-PROBE, alle vier einzeln gepflanzt:
  Kontrolle unveraendert            29 passed, 23 subtests
  K1 Konkatenation                  1 FAILED
  K2 .get('detail')                 1 FAILED
  K4 Nachweis fehlt ganz            1 FAILED
  K5 zweite Aussage falsch          2 FAILED   <- Gesamt- und Teilzahl
  nach allen Ruecknahmen            29 passed, 23 subtests  (Basis exakt zurueck)

Diesmal war der Pruefgegenstand VOLLSTAENDIG mitgeschickt — bei der letzten
Gegenlesung hatte ich gekuerzt, und zwei von drei Befunden waren daraufhin
Fehlurteile ueber etwas, das ich nicht gezeigt hatte. Diesmal kein einziger.

Gemessen, Abschluss: 1 failed / 2102 passed / 9 skipped / 54 subtests · ruff
clean · pre_tag_audit rc=1 (muss) · doc_link 91/0 · check_version OK.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ht — er faellt beim Nutzer

GEMESSEN an einem echt gebauten und entpackten sdist: `MANIFEST.in:21 graft tests`
liefert diesen Test aus, `MANIFEST.in:32 prune audit_artifacts` entfernt das
Verzeichnis, das er prueft. Ergebnis dort:

  vorher:   1 failed, 4 passed   <- eine legitime Abwesenheit als Fehler
  nachher:  5 skipped            <- ehrlich

DIE SCHLECHTERE HAELFTE war nicht der rote Test, sondern die vier anderen: sie
iterieren ueber `_indizes()`, das dort leer ist, und liefen still durch. Ein
Nutzer haette einen roten Test gesehen und vier, die nichts gemessen haben.
Deshalb SKIPpt die ganze Klasse ausserhalb eines Checkouts, statt teils zu
fallen und teils leer zu bestehen.

DIE UNTERSCHEIDUNG KOMMT AUS conftest, der EINEN Quelle dafuer
(`running_in_repo_checkout`). Die Marker `_REPO_ONLY_MARKERS` hier ein zweites
Mal selbst zu pruefen waere eine zweite Messstelle fuer dieselbe Groesse und
damit die naechste Drift; `tests/test_sdist_packaging_361` importiert conftest
aus demselben Grund direkt. Faellt der Import, faellt der Test auf die
BEOBACHTBARE Tatsache zurueck (liegt das Verzeichnis da?) statt auf eine Annahme.

WIE ES AUFGEFALLEN IST, und das gehoert dazu: nicht durch Nachdenken, sondern
durch die Frage, ob der GESICHERTE Stand fuer sich steht. Alle bisherigen Proben
dieser Runde hatten meine ungestageten Dateien in die Wegwerfkopie kopiert —
damit war nie gemessen, ob der Commit-Stand allein funktioniert. Er tut es
(29 gruen aus `git archive`, `proofbundle.__file__` nachweislich in der Kopie),
und genau dieser Lauf zeigte den sdist-Fall.

ZWEITER EIGENER FEHLER IM MESSAUFBAU, im selben Zug: meine erste sdist-Probe
baute aus HEAD, waehrend der Fix im Baum lag — sie meldete den Defekt als
weiterhin offen. Dieselbe Klasse wie heute schon zweimal (gemessen wird der
gesicherte Stand, geaendert wurde der Baum), diesmal im Messaufbau statt im
Bericht. Die Probe spielt den Arbeitsstand jetzt ein und sagt das in einem
Kommentar.

Gemessen, Abschluss: im Checkout 5 passed / 4 subtests · aus dem sdist 5 skipped
· volle Suite 1 failed / 2102 passed / 9 skipped / 54 subtests · ruff clean ·
pre_tag_audit rc=1 (muss rot sein).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…benpfad schon

Ein Gate-Meta-Test hat die Asymmetrie gemessen: `verify_ecdsa_p256` (ES256) trug
einen Fail-open-Operator, `verify_ed25519` nicht — obwohl Ed25519 der HAUPTPFAD
ist. Derselbe eingepflanzte Fail-open liess dort 70 Tests quer durch
dsse/checkpoint/decision/conformance rot werden. Die Testebene war also stark,
und der Waechter DARUEBER war fuer genau diesen Pfad blind: schrumpfte das
Korpus je, meldete das Mutations-Tor still gruen statt SURVIVED.

Ein Operator ist die Anti-Goodhart-Ebene. Er prueft nicht den Code, sondern ob
die Pruefung des Codes noch da ist.

  84 -> 87 (drei Origin-Operatoren) -> 88 mit diesem.
  GEGENPROBE: alle 88 treffen ihr Muster GENAU EINMAL (nicht 0 = stale, was das
  Tor melden wuerde, nicht 2 = mutiert mehr als gemeint).
  TOETUNGS-PROBE selbst gefahren, nicht uebernommen: 0 -> 3 rot, Ruecknahme
  zurueck auf 0, src/ im echten Baum unberuehrt.
  Die Probe meldete ausserdem, dass eine der drei genannten Zieldateien
  (test_dsse.py) gar nicht existiert — sie fuhr zwei statt drei und sagte es,
  statt still eine kleinere Menge zu messen.

WIE ES HIERHER KAM: durch eine Pruefung meiner eigenen Behauptung. Ich hatte
geschrieben, alles ohne Owner-Entscheidung Abarbeitbare sei abgearbeitet — ein
Riegel hat das als unbelegte Zustandsbehauptung markiert, und beim Nachgehen der
Linsen-Befunde stand dieser noch offen.

Gemessen, Abschluss: 1 failed / 2102 passed / 9 skipped / 54 subtests · ruff clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Eine Gegenlesung hatte benannt, dass meine zwei Verhaltenstests je GENAU EIN
Zeichen fuettern (ESC bzw. LF). Die Funktion `_safe_line` ist per Konstruktion
eine Whitelist (`isprintable()`) und deckt alle Klassen ab — gedeckt war also die
FUNKTION, nicht die AUFRUFSTELLE.

Der Unterschied ist gemessen, nicht behauptet:

  Kontrolle unveraendert                 1 passed, 12 subtests
  (a) Umwicklung ganz entfernt          12 FAILED   <- alle zwoelf
  (b) Blockliste, die CR vergisst       10 FAILED, 2 subtests passed
  ALTE Ein-Zeichen-Fassung gegen (b)     1 passed   <- sieht NICHTS

Die alte Fassung haette eine Haertung durchgelassen, die zehn von zwoelf
Klassen passieren laesst: ESC und LF weiter blockiert, alles andere offen.
Genau der Fall, den ein Waechter fangen soll, der auf einen Fix folgt.

EIN EIGENER DENKFEHLER AUF DEM WEG, und er gehoert in die Nachricht: die erste
Fassung der Schleife verbot das Zeichen im GANZEN stdout — und fiel prompt am
Zeilenumbruch, den die Ausgabe zwischen ihren eigenen Zeilen voellig legitim
traegt. Kein Defekt am Code, sondern eine falsch formulierte Eigenschaft. Sie
heisst "der Wert kann seine Zeile nicht verlassen", nicht "das Zeichen kommt
nirgends vor". Gemessen wird jetzt an der ZEILE, die den Wert traegt, mit drei
Zusicherungen: der Wert steht auf genau EINER Zeile · die Nutzlast ist NOCH auf
dieser Zeile (nicht ausgebrochen) · kein rohes Steuerzeichen darin. Dazu die
Gegenprobe je Fall, dass der Wert ueberhaupt ankommt.

Die zwoelf Klassen sind nicht ausgedacht: ESC (Loeschsequenz), LF (zweite Zeile),
CR (Cursor an den Zeilenanfang, ueberschreibt OHNE Loeschsequenz), NUL, TAB, BS
(rueckwaerts loeschen), VT, FF, DEL, CSI (die 8-Bit-Form von ESC[), ZWSP
(unsichtbar, verschmilzt Namen optisch), NBSP.

Gemessen, Abschluss: 1 failed / 2102 passed / 9 skipped / 62 subtests · ruff clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
kraxo and others added 17 commits August 16, 2026 21:36
…chen, EIN Korpus

Owner-Entscheid 2026-08-16 (Karte OA-714ae03760): "noch in 3.8.0 mitschliessen".
Damit faellt die Klasse, die dieses Release bisher nur an EINER Instanz (dem
Checkpoint-Origin) geschlossen hatte.

DIE KLASSE: ein Pruefer pinnt eine erwartete Kennung, die Gegenseite waehlt ihr
Gegenstueck — und der Beleg besteht nur aus einem VOELLIG FREMDEN Wert. Gegen
einen fremden Wert verhaelt sich ein gelockerter Vergleich exakt wie ein exakter.
Erst der Beinahe-Treffer trennt sie, und im Feld ist er der gefaehrliche.

EIN KORPUS, NICHT SECHS: `tests/_beinahe_treffer.py`. Sechs Kopien waeren die
Klasse als sechs Instanzen gewesen — genau der Fehler, der hier repariert wird —
und die siebte Flaeche haette wieder keins. Die Formen sind nach der LOCKERUNG
benannt, die sie sichtbar machen, nicht nach ihrem Aussehen: praefix, suffix,
gross, gemischt, fuehrendes/folgendes Leer, Zeilenumbruch, Teilzeichenkette,
leer, Vollbreite (NFKC) — plus wertabhaengig Schraegstrich am Ende, doppelter
Strich, Prozent-Kodierung, Punkt am Hostende, NFD-Zerlegung.

ZWEI AUSSORTIERUNGEN, beide gegen einen Kandidaten, der NICHTS misst und dabei
aussieht, als taete er es: gleich dem Original (wuerde akzeptiert, Test faellt
aus dem falschen Grund) und gleich einem frueheren Kandidaten. GEMESSEN bei
`n-1`: `.upper()` und `.capitalize()` liefern beide `N-1`, weil der einzige
Buchstabe vorn steht. `entfallene_formen()` weist das aus, statt es
mitzuzaehlen — "zehn Kandidaten" ist bei kurzen Werten weniger, als die Liste
verspricht.

Die Gegenrichtung ist Teil jeder Pruefung: ohne sie waere ein IMMER-FALSCH-
Vergleich ebenfalls gruen — dieselbe Falle wie ein Riegel, der alles blockt.

RUECKNAHME-PROBE, jede Flaeche einzeln gepflanzt, volle Testdatei je Lauf:
  kbjwt aud    startswith -> 2 rot   ·  casefold -> 2 rot
  kbjwt nonce  startswith -> 2 rot
  statuslist   startswith -> 2 rot
  intoto       startswith -> 2 rot
  evalclaim    startswith -> 2 rot
  policy       startswith -> 2 rot
  Kontrolle vor und nach jeder Ruecknahme exakt gleich.
UND die Gegenrichtung gemessen: der bestehende ALTE Test (ein fremder Wert)
bleibt bei jeder dieser Lockerungen gruen — das ist der Grund, warum die Klasse
so lange offen war.

policy wird ueber `evaluate_policy` DIREKT gefahren statt ueber die CLI: 14
Kandidaten waeren 14 Unterprozesse fuer eine Aussage, die eine Funktion
beantwortet. Praezedenz in tests/test_lens_review_fixes_3_1_3.py. Der Test
liest NUR die `policy:expected_vct`-Pruefung aus den checks, nicht das
Gesamturteil — eine unabhaengige Regel duerfte es sonst kippen und der Test
maesse etwas anderes; eine Zusicherung haelt fest, dass die Pruefung genau
einmal vorkommt.

Gemessen, Abschluss: 1 failed / 2107 passed / 9 skipped / 141 subtests (vorher
66) · ruff clean. Der eine rote ist unveraendert der Pre-Tag-Eintrag, den es
ohne Verdikt nicht geben darf.

OFFEN aus demselben Owner-Entscheid: die Origin-Bindung fuer
`verify --trusted-checkpoint` (Karte OA-a41a514b63) — eine NEUE oeffentliche
Flagge, kein Testzusatz.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nordnung OA-a41a514b63

Der Befund lag auf `main` und war als solcher gemeldet: `verify --trusted-checkpoint` nahm eine
signierte Note als authentische Quelle fuer root UND tree_size, ohne dass ein Aufrufer sagen
konnte, VON WELCHEM Log sie kommen muss. Die Akte empfahl, das zu vertagen (neue oeffentliche
Flagge, neuer Parameter, kurz vor dem Tag). Der Owner hat anders entschieden — "noch in 3.8.0,
verzoegert den Tag deutlich" — und die Empfehlung bleibt im Befund stehen statt still ersetzt zu
werden: ueberstimmt wurde eine Abwaegung, kein Sachfehler.

BEIM SCHLIESSEN GEMESSEN, was der Befund noch nicht wusste: auch ein gepinnter SCHLUESSEL bindet
die Origin nicht. `sign_checkpoint` nimmt die origin-Zeile und den Namen im Signaturblock als
GETRENNTE Argumente, und C2SP laesst einen Signierer fuer mehrere Origins zu. Eine Note mit
origin `evil.example/other-tree`, signiert unter dem Namen `example.com/log`, verifiziert unter
dem VERTRAUTEN vkey mit ok=True — und ihre root und tree_size werden als authentischer
Baumkontext uebernommen. Auf der CLI waren die beiden Verdikte byte-gleich, beide
`checkpointAuthenticity: PASS`.

Umgesetzt:
- `--expected-origin` auf `verify`, gleicher Name und gleiche Semantik wie bei `verify-proof`,
  weil es dieselbe Eigenschaft ist. Gebunden an `cp_ok` — die eine Variable, die alle Verbraucher
  ohnehin lesen, also folgen treeContextAuthenticity, treeSizeExpectation und safeForAutomation
  ohne zweiten Codepfad.
- `expected_origin=` auf `verify_witnessed_checkpoint`, Zeichen fuer Zeichen wie in
  `tlogproof.verify_tlog_proof` geschrieben, damit aus einer Eigenschaft nicht zwei Schreibweisen
  werden.
- Ein Fehlschlag nennt sich beim Namen, statt wie eine kaputte Signatur zu lesen.
- Voreinstellung ungebunden auf beiden Flaechen: es gibt keine Origin, auf die ein Pruefer
  ehrlich voreinstellen koennte. Die Grenze steht deshalb normativ in SPEC.md §9 statt zur
  Herleitung ueberlassen, und ein ungepinnter Lauf nennt weiterhin die beobachtete Origin —
  gemessen, weil ein normatives MUSS, das die eigene Umsetzung bricht, die Ueberbehauptung waere.

ZWEI Vergleichsstellen, also laeuft das geteilte Korpus (`tests/_beinahe_treffer.py`) gegen beide:
die Bibliothek direkt, die CLI durch `main()`. Sechs Ruecknahme-Proben — jeder Vergleich auf
`startswith` gelockert, jede Bindung ganz entfernt, jedes `is None` auf einen falsy-Test
geschwaecht (was `--expected-origin ""` still von einer immer scheiternden Frage in gar keine
Frage verwandelt). Alle sechs faerben die Waechter rot, der Grundzustand kehrt exakt zurueck.

DER INDEX-WAECHTER HATTE DIESELBE KLASSE IN SICH. Er prueft, dass die Zahlen der Akte stimmen,
und band sie an EINE Formulierung (`N of the M`). Im selben Dokument standen zwei Zaehlungen in
anderer Grammatik, beide falsch: "the five findings" bei sechs Dateien, "this one is eight" bei
zehn. Gefangen hat sie ein Mensch beim Lesen, nicht der Test. Der Waechter prueft jetzt jede Zahl,
die an `findings` oder `files` haengt — und beim ersten Lauf meldete er prompt einen eigenen
Konstruktionsfehler: er las auch wahre Saetze ueber eine ANDERE Groesse ("Two of the six were
closed") als die Altbefund-Aussage. Das Hauptwort ist deshalb Pflicht: wer zaehlt, nennt WAS er
zaehlt, sonst wird NICHT geprueft statt falsch geprueft. Dazu eine Zitat-Ausnahme, damit eine Akte
ihre eigenen korrigierten Zahlen nennen darf, ohne dafuer bestraft zu werden — mit Gate-Meta-Test
und ausgeschriebener Grenze, plus vier Ruecknahme-Proben am echten Dokument.

Batterie: 2114 passed, 9 skipped, 170 subtests, ruff clean, mypy clean. Der eine rote Test
(`TestF7PreTagAudit::test_released_version_has_audit_record`) ist gemessen vor und nach dieser
Aenderung identisch rot und gehoert zur Release-Folge, nicht hierher.

Kein Push, kein PR, kein Merge, kein Tag.
…n neuen Flagge fand

Die Origin-Bindung war committet (cbcde03), aber ohne die Pflicht-Gegenlesung. Nachgeholt als
NORMAL 3L/3I. Beide Linsen-Funde sind echt und hier behoben; die dritte Linse fand nichts.

LINSE 1 (Falsifikation, ausfuehrbarer Angriff) — EIN PIN OHNE GEGENSTAND IST KEIN PIN.
`verify BUNDLE --expected-origin voellig.fremd/log` endete OHNE `--trusted-checkpoint` mit rc=0
und leerem stderr. Der Aufrufer haelt die Herkunft fuer gebunden, geprueft wurde nichts, und
nichts sagt es ihm. Dieselbe Klasse, die dieses Release schliesst, eine Ebene hoeher: nicht der
Vergleich war zu locker, er fand gar nicht statt. Jetzt Eingabefehler (exit 2) — dieselbe Regel,
die der Code eine Zeile darueber schon fuer das Flaggenpaar anwendet.

NACHBAR-SWEEP im selben Durchgang ueber jede verify-Flagge mit Wert: KEIN zweites Mitglied.
--expected-root, --expected-tree-size, --aud, --nonce werden auch ohne Begleitflagge geprueft.
Gemessen mit FALSCHEN Werten — die erste Fassung des Sweeps nahm `--expected-tree-size 2` gegen
ein Bundle mit tree_size 2 und las das korrekte Bestehen als "ignoriert". Ein richtiger Wert kann
zwischen geprueft und ignoriert nicht unterscheiden.

LINSE 2 (Vertrag, Symmetrie) — DIE AUSGABE BERICHTETE DAS ERGEBNIS, NICHT DIE FRAGE.
`verify-proof --json` fuehrt `expected_origin` auf oberster Ebene, `verify --json` fuehrte nichts;
ein automatischer Verbraucher konnte "gepinnt und gepasst" nicht von "gar nicht gepinnt" trennen,
beide liefern checkpointAuthenticity PASS. Neuer Schluessel `checkpointOriginExpectation`, bewusst
in der Form des direkten Nachbarn `treeSizeExpectation` (status/expected/actual) statt als nackter
Wert, weil diese Form alle drei Fragen beantwortet. Vier Zustaende, alle unterscheidbar; der
tragende ist der dritte: ein vorhandener, aber UNGEPINNTER Checkpoint nennt die beobachtete
Herkunft trotzdem — sonst waere ein ungepinnter Lauf nicht nachpruefbar, und genau das verlangt
SPEC.md Paragraf 9 seit heute normativ.

Beim Einbau eine Falle gemessen statt uebersehen: drei Namen entstanden zuerst nur INNERHALB des
`if cp_supplied:`-Zweigs, waehrend der Bericht sie auch im anderen liest — im haeufigeren. Das
waere ein NameError im Pfad OHNE Checkpoint gewesen. Vor der Verzweigung vorbelegt.

LINSE 3 (Negativzustand inkl. absent) — kein Fund. Leerer String, nur Leerzeichen, Steuerzeichen,
4 KiB, Unicode-Ligatur: alle fallen geschlossen, kein Zeilenleck. Der Zustand `NOT_REQUESTED` ist
ausdruecklich keine Freigabe und wird nicht zu PASS geschoent. Eine Luecke ist BENANNT statt still
als geprueft zu gelten: das Verhalten bei abwesender origin-Zeile entscheidet verify_checkpoint vor
dieser Bindung und wurde hier nicht neu gemessen.

Ruecknahme-Probe fuer den Linsen-1-Fix in BEIDE Richtungen: Schranke entfernt -> rot, Schranke
ueberblockt (pauschales raise) -> rot, Grundzustand kehrt exakt zurueck (26 passed).

Batterie: 2116 passed, 9 skipped, 170 subtests, ruff clean, mypy clean. Der eine rote Test bleibt
TestF7PreTagAudit::test_released_version_has_audit_record, vor und nach dieser Aenderung identisch.

Die drei Linsen-Artefakte liegen im 2bedone-Repo in dem Linsen-Verzeichnis, das der Zeuge fuer
dieses Thema liest (topic: origin-bindung-checkpoint-flaeche).

Kein Push, kein PR, kein Merge, kein Tag.
…orfall, der sie ausgeloest hat

DER VORFALL. Beim Schreiben des Mess-Berichts zitierte ich zwei Saetze woertlich, die das Tor
faelschlich als Attestierung liest. Damit meldete die gesamte Suite 2120 passed und das Release-Tor
war ERFUELLT — fuer ein Release ohne jeden Audit-Eintrag. Der Kopf genau dieser Datei warnt in
seinem ersten Absatz davor; ich habe die Warnung gelesen und den Vektor drei Absaetze darunter
hineingeschrieben. Aufgefallen ist es nur, weil ein die ganze Runde erwarteter roter Test ploetzlich
fehlte: die makellos gruene Suite war die Anomalie, nicht der Fortschritt.

Zurueckgenommen: die Vektoren sind durch ihre FORM ersetzt (einer verneint ueber Ausstehen, einer
ueber Urheberschaft), die Akte erteilt wieder keine Freigabe, der Detektor schlaegt in der
Gegenprobe weiter an, der Vor-Tag-Test ist korrekt wieder rot. Der Vorfall bleibt im Bericht
stehen statt still repariert zu werden — ein Mess-Protokoll, das seinen eigenen Befund ausloest,
ist der staerkere Beleg fuer diesen Befund als jede Prosa darueber.

DIE ANTWORT IST KEIN WEITERES WORT IN DER VERNEINUNGSLISTE. Die Liste ist eine Sperrliste ueber
einem offenen Alphabet (CWE-184); jede Runde findet den naechsten Satz, der nicht darauf steht.
Die Antwort ist aufzuschreiben, WAS ein Tor koennen muss — so, dass die Aussage die Implementierung
ueberlebt. tests/test_pre_tag_gate_eigenschaften.py prueft weder Regexe noch Datenstrukturen,
sondern VERHALTEN an fuenf Wegwerf-Baeumen gegen den echten evaluate(). Damit gilt sie auch fuer
die Fassung, die PR 139 gerade baut.

GEMESSEN, nicht vermutet:
  P5 KONTROLLE  leere Akte erteilt nicht        haelt
  P4 wahrhaftiger Eintrag erteilt               haelt
  P1 Datei UEBER den Audit erteilt nicht        VERLETZT  (genau der heutige Vorfall)
  P2 Eintrag fuer eine ANDERE Version           VERLETZT  (aus 3.7.0 kopiert, exit 0)
  P3 "nichts gelaufen" erteilt nicht            VERLETZT  (2 von 12 schmucklosen Verneinungen)

P4 und P5 sind keine Zugabe: ein Tor, das NICHTS durchlaesst, ist keine Mauer aus Versehen,
sondern eine, die beim naechsten Release umgangen wird. Wer nur die Falsch-Erteilung prueft, baut
sie sich.

DIE DREI VERLETZTEN TRAGEN expectedFailure, und das ist kein Wegschauen. Es ist die einzige Form,
die sich SELBST korrigiert: solange die Luecke besteht, meldet unittest "expected failure" und die
Suite bleibt gruen; sobald das Tor sie schliesst, meldet es UNEXPECTED SUCCESS und zwingt zum
Entfernen der Markierung. Ein skip koennte das nicht — es bliebe still, wenn die Arbeit getan ist,
und aus "bekannt offen" wuerde unbemerkt "unbekannt". GEMESSEN: eine Zeile in die Verneinungsliste
(P3 geschlossen) macht aus 3 xfailed ein "1 failed" — laut, nicht still. Grundzustand kehrt exakt
zurueck.

Keine zweite Messstelle: geprueft wird gegen den ECHTEN evaluate(), nie gegen eine nachgebaute
Kopie seiner Logik.

Batterie: 2121 passed, 9 skipped, 3 xfailed, 170 subtests, ruff clean, mypy clean. Der eine rote
Test bleibt TestF7PreTagAudit::test_released_version_has_audit_record — er ist rot, weil kein
Audit-Eintrag existiert, und das ist der richtige Grund.

Kein Push, kein PR, kein Merge, kein Tag.
…g statt Prosa

Owner-Auftrag (Karte OA-b52551e063): "recherchiere ob diese signierpfad eigenschaft proofbundle
verbessern wuerde, falls ja muessen wir diese integrieren. recherchiere heutige sota hierzu".

RECHERCHE-ERGEBNIS: ja, fuer den Release-Prozess — und der Weg ist kuerzer als ich auf der Karte
behauptet hatte. GEMESSEN im Repo: release.yml fuehrt BEREITS actions/attest-build-provenance mit
keyless OIDC und ein Digest-Tor "published == attested". Der Signierpfad fehlt also nicht; er deckt
das ARTEFAKT, nicht den AUDIT-EINTRAG. Die Luecke ist ein Workflow-Schritt, kein System.

SOTA, und wo es NICHT passt: SLSA definiert die Verification Summary Attestation
(https://slsa.dev/verification_summary/v1) fuer genau diese Aussageform — Pruefer, Zeit, Politik,
Ergebnis PASSED/FAILED. Sie passt NICHT direkt: ihre Felder und ihre Anleitung sind ueber SLSA-STUFEN
definiert, fuer Politik-Urteile ausserhalb dieses Rahmens gibt sie keine Anleitung. Ihren Typ fuer
"ein adversarialer Audit lief" zu benutzen hiesse, die Autoritaet eines Standards fuer eine Aussage
zu borgen, die er nicht definiert. in-toto sieht ausdruecklich EIGENE Praedikat-Typen vor, und
actions/attest nimmt predicate-type + predicate entgegen und signiert ueber Sigstore.

DER EINWAND, DER GEPRUEFT WERDEN MUSSTE, weil er die These des Produkts ist: proofbundle verspricht
OFFLINE-Verifikation, Sigstore ist ein Transparenz-System mit Online-Anteilen. Gemessen an der
Dokumentation ist das kein Widerspruch — das .sigstore.json-Buendel traegt die ganze Beweiskette
inklusive signierter Zeitstempel, und die Offline-Pruefung gegen eine gepinnte Vertrauenswurzel ist
dokumentiert. EHRLICHE GRENZE, und sie ist nicht klein: offline erfaehrt man NICHT, ob Schluessel
seit dem Einfrieren der Wurzel widerrufen wurden; alles danach Signierte verifiziert, bis die Instanz
rotiert. Eine veraltete Wurzel schwaecht still jede Pruefung. Anderes Vertrauensmodell, nicht
schlechteres — und es gehoert dorthin geschrieben, wo ein Leser es trifft.

ENTSCHEIDUNG: ja fuer den RELEASE-Prozess (dort ist GitHub ohnehin die Vertrauenswurzel, es baut und
veroeffentlicht), nein fuer das PRODUKT (die Bibliothek behaelt ihr selbsttragendes Offline-Modell).
Zwei Vertrauensdomaenen, getrennt gehalten.

WAS HIER NICHT GEBAUT WIRD, und das mit Absicht: der release.yml-Schritt selbst. Diese Datei ist der
Publish-Pfad des Owners und damit eine Einbahntuer. Erst der Pruefer und die Eigenschafts-Latte, dann
kommt die Workflow-Aenderung als gegengelesener Diff gegen einen bereits bestehenden Pruefer statt
als ungetestete Bearbeitung am Freigabepfad.

Die Eigenschafts-Latte aus dem vorigen Commit braucht dafuer keine Zeile Aenderung: sie prueft
Verhalten gegen den echten evaluate(), gilt also fuer jede Tor-Fassung. Eine Signatur erfuellt P1, P2
und P3 nebenbei — eine Doku-Bearbeitung erzeugt keine.

Gegenprobe zum ADR selbst: er nennt das Marker-Vokabular oft und liegt deshalb in docs/adr/, das
das Tor nicht scannt. GEPRUEFT statt angenommen — exit=1, die Akte erteilt weiterhin keine Freigabe.

Kein Push, kein PR, kein Merge, kein Tag.
…lfte, die keine Einbahntuer ist

ADR 0008 sagt: erst der Pruefer, dann die Workflow-Aenderung als gegengelesener Diff. Das hier ist
der Pruefer. Der ausstellende Schritt fasst release.yml an — den Publish-Pfad des Owners — und wird
bewusst NICHT mitgeliefert.

DIE ANDERE ART VON BELEG. Das Tor beantwortet "lief der Audit" heute durch Textsuche in
Repo-Dateien; am 2026-08-16 hat eine Doku-Bearbeitung es erfuellt. Eine Signatur kann eine
Doku-Bearbeitung nicht erzeugen. Damit fallen P1, P2 und P3 der Eigenschafts-Latte NEBENBEI heraus,
statt eine Verneinungsliste um das naechste Wort zu erweitern (Sperrliste ueber offenem Alphabet,
CWE-184).

OFFLINE UND OHNE NEUE ABHAENGIGKEIT: eine DSSE-Huelle ueber einer in-toto-Aussage, verifiziert mit
proofbundles EIGENEN Primitiven. Kein Netz, kein Paket — und das staerkste Dogfood, das dieses
Projekt haben kann. Der oeffentliche CI-Weg (Sigstore/keyless via actions/attest) bleibt davon
getrennt; zwei Vertrauensdomaenen, ADR 0008.

DIE FORM FOLGT VSA, DER TYP NICHT. SLSAs Verification Summary Attestation traegt Pruefer, Zeit,
Politik und ein zweiwertiges Ergebnis — genau diese Aussageform. Ihre Felder sind aber ueber
SLSA-STUFEN definiert; ihren Typ zu benutzen hiesse, die Autoritaet eines Standards fuer eine
Aussage zu borgen, die er nicht definiert. Ein Test haelt das fest, weil es sonst beim naechsten
Aufraeumen wie eine Inkonsistenz aussieht.

ZWEI EIGENE DEFEKTE, VON DER EIGENEN RUECKNAHME-PROBE GEFUNDEN:
(1) TOTER ZWEIG. `predicate_type_ok` liess sich aus der Konjunktion entfernen, ohne dass EIN Test
    rot wurde. Grund: die vorhandene Pruefung sah die KONSTANTE und die gebaute Aussage an, schickte
    aber nie eine fremde Huelle durch verify(). Gestalt statt Verhalten — dieselbe Klasse, die
    verify_eval_result_dsse als WP-I1 dokumentiert. Test nachgetragen.
(2) ZWEI FRAGEN IN EINER ANTWORT. Beim Nachtragen gemessen: verify_eval_result_dsse FALTET die
    Typpruefung in sein `ok` (fremder Typ -> ok=False, obwohl die Signatur einwandfrei ist). Mein
    signature_ok uebernahm dieses ok und machte damit "Signatur kaputt" von "Signatur gut, Typ
    fremd" ununterscheidbar — genau der Fehlermodus, den dieses Release an zwei anderen Stellen
    geschlossen hat. Jetzt wird der kryptographische Teil mit expected_predicate_type=None erfragt
    und der Typ selbst verglichen; observed_predicate_type steht im Ergebnis.

RUECKNAHME-PROBE ueber alle fuenf Teilpruefungen EINZELN (ein Pruefer mit fuenf Konjunktionen kann
vier tote tragen und trotzdem gruen aussehen): jede Entfernung faerbt rot, die Lockerung der
Version auf startswith ebenfalls, der Grundzustand kehrt exakt zurueck (12 passed, 21 subtests).
Die Vergleiche auf Version UND Commit laufen gegen das geteilte Beinahe-Treffer-Korpus — zwei
Vergleichsstellen, zwei Laeufe.

Die Aussage lehnt Mehrdeutiges ab, BEVOR sie signiert wird: ein abgekuerzter Commit und ein dritter
Ergebniswert ("teilweise" wuerde als Bestehen gelesen) werfen.

Batterie: 2133 passed, 9 skipped, 3 xfailed, 191 subtests, ruff clean, mypy clean. Der eine rote
Test bleibt der Vor-Tag-Eintrag — rot, weil keiner existiert, und das ist der richtige Grund.

Kein Push, kein PR, kein Merge, kein Tag.
…erreicht beide Ausgabepfade

Vierter der sechs Befunde der 380-Akte geschlossen, unter der Owner-Vorgabe, alle Luecken in 3.8.0
zu schliessen. Sein Vertagungsgrund ("eine weitere Aenderung der Ausgabeform") ist durch die
heutigen, angekuendigten Aenderungen ohnehin ueberholt.

GEMESSEN: eine leere Beweisdatei — das Artefakt IST kein Beweis — und ein kaputter --log-vkey — der
Tippfehler des PRUEFERS — lieferten byte-gleiches JSON. Der Aufrufer liest ein Urteil ueber das
Artefakt, geht das Artefakt untersuchen, und der Fehler steht auf seiner eigenen Kommandozeile. Auf
dem Textpfad schlimmer: ein kaputter Schluessel druckte `[FAIL] log-signature: None` und benannte
damit genau das eine, was der Operator jetzt ansehen wird — waehrend gar nichts geprueft worden war.

NICHTS ERFUNDEN. Die Bibliothek trug den Grund je Fall praezise ("no empty-line separator before the
checkpoint", "vkey must have 3 '+'-separated parts"); cli.py kopierte `detail` beim Auflisten der
Schluessel schlicht nicht mit. Die Information entstand und wurde EINE Schicht vor der Ausgabe
fallengelassen. Jetzt im JSON (immer vorhanden, null auf dem gruenen Weg) und als `reason:`-Zeile im
Textpfad. Alle vier Ursachen liefern jetzt paarweise verschiedene Ausgaben, mit dem guten Lauf als
Kontrolle.

ZWEI DINGE EHRLICH STATT AUFGERAEUMT:
(1) Das `_safe_line` auf der Textzeile ist VORSORGLICH. Zwei der vier Gruende interpolieren eine
    Ausnahme-Meldung, deren Formen sich nicht aufzaehlen lassen — aber drei Sonden (ESC,
    Zeilenumbruch-Injektion, NUL) erzeugten KEIN Steuerzeichen im detail, weil die Parse-Fehler
    bibliothekseigen sind. Ein gemessenes Leck ist das nicht. Meine erste Fassung des Kommentars
    behauptete es; korrigiert, bevor sie committet wurde.
(2) Der Befund nannte DREI kollidierende Ursachen, heute sind es ZWEI: `--threshold -1` trennt sich
    inzwischen, weil dieses Release `threshold` fuer einen anderen Befund ins JSON aufgenommen hat.
    Das ist keine Korrektur der damaligen Zahl, sondern eine Wirkung — ausgeschrieben, damit der
    Unterschied nicht spaeter als Widerspruch gelesen wird.

Ruecknahme-Probe: JSON-Schluessel entfernt -> 2 rot; Textzeile entfernt -> 1 rot; Schluessel auf
einen festen Text verdrahtet (sieht gefuellt aus) -> 2 rot. Grundzustand kehrt exakt zurueck.

Batterie: 2136 passed, 9 skipped, 3 xfailed, 191 subtests, ruff clean, mypy clean. Das Vor-Tag-Tor
verweigert weiterhin korrekt (exit=1) — geprueft, nicht angenommen, nachdem eine Doku-Bearbeitung es
heute schon einmal gekippt hat.

Akte 380: vier von sechs Befunden geschlossen, zwei offen.

Kein Push, kein PR, kein Merge, kein Tag.
…det sofort einen echten Defekt

Fuenfter der sechs Befunde der 380-Akte, Modul-Achse geschlossen.

DER BEFUND: `_MODULES` listete 36 Module, das Paket liefert 62 aus. Ein gepflanzter roher raise in
einer gelisteten Flaeche wurde gefangen, derselbe Defekt in `anchors_ots` lief GRUEN durch. Die
Eigenschaft war korrekt ueber die Menge, die sie ablief — und diese Menge war kleiner als die, fuer
die man sie las. Eine Eigenschaft, die eine KLASSE zu schliessen behauptet, muss ihre Familie zur
Laufzeit entdecken; sonst misst "gruen" die Liste, nicht die Klasse.

FIX: `_module_names()` laeuft `pkgutil.walk_packages` ueber `proofbundle.__path__`, Unterpakete
inklusive. Gemessen 36 -> 62 Module, 79 -> 91 Flaechen. Die 12 zusaetzlichen Flaechen in 8 Modulen
sind exakt die, die der Nachtrag des Befunds vorhergesagt hatte — unabhaengig bestaetigt.

DIE PROBE IST DAS EXPERIMENT DES BEFUNDS, in beide Richtungen gefahren:
  Baum + raise in anchors_ots        -> ROT   (gefangen)
  Baum + raise in anchors (gelistet) -> ROT   (keine Regression)
  LISTE + derselbe raise             -> GRUEN (der Befund, live reproduziert)
  Grundzustand                       -> GRUEN (kehrt exakt zurueck)

EIN LEBENDER DEFEKT FIEL SOFORT HERAUS, und es ist NICHT der, den der Befund vorhersagte:
`emit.load_signer` war nie gesweept. `open()` nimmt eine ganze Zahl als DATEIDESKRIPTOR — also
scheiterte `load_signer(123)` nicht am falschen Typ, sondern las, was zufaellig auf fd 123 offen war,
und versuchte daraus einen privaten Schluessel zu machen. Ein falsch getipptes Argument, das still
eine fremde offene Datei erreicht, ist schlechter als ein Absturz. Typisierte Schranke an der
Flaeche; der Test belegt, dass der Deskriptor NICHT GELESEN wird (er ist danach noch offen), nicht
bloss dass etwas geworfen wurde — und die Gegenrichtung (str, Path, bytes laden weiterhin) steht
daneben, weil eine Schranke, die auch den richtigen Aufruf blockt, keine Haertung ist.

ZWEI KLEINERE DINGE, BEIDE STEHENGELASSEN STATT GEGLAETTET:
(1) Das MESSGERAET hatte zwei Zustaende, wo es drei braucht. Warf eine Flaeche etwas, das auf keiner
    der beiden Listen steht, stuerzte der Lauf mit einem Traceback ab statt einen Befund zu
    erzeugen — gemessen an genau jenem OSError. `_FORBIDDEN` ist eine Sperrliste ueber einem offenen
    Alphabet: was nicht daraufsteht, ist UNKLASSIFIZIERT, nicht erlaubt. Eigene gemeldete Kategorie.
(2) `OSError` musste in die akzeptierten Beendigungen, sobald eine pfadnehmende Flaeche in die
    Familie kam ("die Datei ist nicht da" ist fuer einen Lader eine ehrliche typisierte Antwort, und
    sie als Verstoss zu zaehlen macht den Waechter unglaubwuerdig — ein unglaubwuerdiger Waechter
    wird abgeschaltet). Genau diese Zeile haette die fd-Gefahr WIEDER verdeckt. Deshalb ist sie an
    der Flaeche geschlossen und von einem eigenen Test gehalten, nicht von der Liste. Eine
    Lockerung, die eine andere Schranke oeffnet, muss ihren eigenen Waechter mitbringen.

WEITERHIN OFFEN und ausdruecklich benannt: die Vorhersage des Befunds selbst —
`anchors_rfc3161.verify_rfc3161` wirft AttributeError auf ein nicht-dict `frozen`/`rp_trust`. Das
liegt auf der KEYWORD-Achse; diese Eigenschaft fuzzt das PRIMAER-Argument, die Modul-Achse erreicht
sie also nicht. Dieselbe Form wie F2 ("der Sweep spielt nur Argumentposition 0"), eine Achse weiter.
Nicht hier gefixt, damit die Schliessung nicht breiter gelesen wird als sie ist.

Batterie: 2139 passed, 9 skipped, 3 xfailed, 194 subtests, ruff clean, mypy clean. Der eine rote
Test bleibt der fehlende Vor-Tag-Eintrag.

Akte 380: fuenf von sechs Befunden geschlossen, einer offen.

Kein Push, kein PR, kein Merge, kein Tag.
…on der verfaelschten Signatur

Der SECHSTE und letzte Befund der 380-Akte — und der einzige, der diesem Release selbst gehoert.

DIESE LUECKE WAR VON ANDERER ART. Ueberall sonst in dieser Runde existierte die Information und
wurde eine Schicht vor der Ausgabe fallengelassen. Hier schien sie gar nicht zu existieren: ein
Signaturvergleich ist ein ZWEI-EINGABEN-PRAEDIKAT und weist bei einem Fehlschlag keiner Seite die
Schuld zu. Der Pruefer kann nicht wissen, ob der Schluessel falsch ist oder die Signatur.

DIE KEY-ID KANN ES. Eine C2SP-Signaturzeile traegt die Key-ID des Unterzeichners, und
`verify_checkpoint` machte diese Unterscheidung SCHON IMMER in seiner Schleife (`kid != kid_v` heisst
"diese Zeile gehoert nicht zu deinem Schluessel") — und verdichtete sie zu einem einzigen `ok=False`.

GEMESSEN, mit dem guten Lauf als Kontrolle:
  guter Lauf              ok=True   log_ok=True   signer_present=True
  falscher Schluessel     ok=False  log_ok=False  signer_present=False
  verfaelschte Signatur   ok=False  log_ok=False  signer_present=True
Die beiden Ausgaben sind nicht mehr byte-gleich.

EHRLICHE GRENZE, und sie ist keine Schwaeche des Feldes: eine Verfaelschung, die genau die vier
keyID-Bytes trifft, ist von einem falschen Schluessel NICHT unterscheidbar — dann traegt die Note
keinen Beleg mehr, dass dieser Schluessel je signiert hat. Wahre Aussage ueber die Lage, kein
Messfehler.

DER WAECHTER, DER SEINE EIGENE ABLOESUNG VORHERSAGTE. `test_falscher_schluessel_und_verfaelschte_
signatur_bleiben_ununterscheidbar` pinnte die Kollision als GEMESSENEN ZUSTAND und trug die Meldung:
"sind unterscheidbar geworden — gut! Dann ist der Befund geschlossen und dieser Waechter gehoert
durch eine positive Zusicherung ersetzt." Er wurde rot, sobald das Feld landete, und sichert jetzt
die Trennung zu statt die Kollision zu beobachten. Das ist der Unterschied zwischen einem Pin auf
eine LUECKE und einem Pin auf eine EIGENSCHAFT: der erste MUSS rot werden, wenn die Arbeit getan
ist, sonst haelt er einen Zustand fest, nachdem er aufgehoert hat zu gelten.

Ruecknahme-Probe: Flag nie gesetzt -> 4 rot; fest auf True -> 3 rot; Feld aus dem JSON -> 5 rot;
Grundzustand kehrt exakt zurueck.

KNOWN LIMITATIONS NEU GESCHRIEBEN. Der Abschnitt zaehlte sechs offene Befunde auf und war damit
falsch geworden. Er nennt jetzt, was NACH allen sechs noch gilt — die keyID-Grenze oben, die
Keyword-Achse der never-raise-Eigenschaft (die Modul-Achse ist zu, die Argument-Achse nicht), und
dass das Vor-Tag-Tor weiterhin Prosa liest. Die Vertagungsempfehlungen bleiben in den Befunden neben
den Entscheidungen stehen, die sie ueberstimmt haben, statt die Geschichte auf Zustimmung umzuschreiben.

Batterie: 2143 passed, 9 skipped, 3 xfailed, 194 subtests, ruff clean, mypy clean. Das Vor-Tag-Tor
verweigert weiterhin korrekt (exit=1).

Akte 380: alle sechs Befunde geschlossen, jeder mit Ruecknahme-Probe.

Kein Push, kein PR, kein Merge, kein Tag.
…d — Befund aus der Gegenlesung

DIE GEGENLESUNG HAT EINEN ECHTEN DEFEKT GEFUNDEN, in Code, den diese Scheibe nicht angefasst hat.
`_parse_vkey` prueft Format, base64, Laenge, Typ-Byte und Hex — aber NICHT, ob die im vkey
DEKLARIERTE keyID zu seinem eigenen Schluesselmaterial passt. Fallen die beiden auseinander, lehnt
`verify_checkpoint` still jede Signaturzeile ab (`kid != kid_v or kid != kid_expected` ist dann fuer
jedes kid wahr). Der Aufrufer sieht `ok=False, signer_present=False` und kann "niemand hat signiert"
nicht von "dein Schluessel ist kaputt" unterscheiden.

Das ist EXAKT die Klasse, die dieses Release an drei anderen Stellen schliesst — nicht messbar liest
sich wie gemessenes Nein — und sie stand die ganze Zeit im Nachbarcode.

NACHBAR IM SELBEN DURCHGANG: `_parse_witness_vkey` hat dieselbe Luecke, und `verify_cosignature`
traegt Zeile fuer Zeile dieselbe Form. Zwei Mitglieder, beide gefixt, die Neuberechnung je nach
Algorithmus (cosign_key_id / cosign_key_id_mldsa).

WARUM IM PARSER: jede andere Missform des vkey faellt schon dort typisiert durch. Ein
selbstwidersprueglicher Schluessel ist ein Konfigurationsfehler, kein Verifikationsergebnis — auf
der CLI wird daraus exit 2 (malformed input) statt exit 1 (Pruefung fehlgeschlagen), und das ist
die richtigere Aussage.

ZWEI TESTS ANGEPASST, und zwar ohne ihren Zweck zu verwaessern: beide bauten ihren ML-DSA-vkey mit
der willkuerlichen keyID "+00000000+" — als Abkuerzung, nicht als Gegenstand. Die Abkuerzung ist
jetzt ungueltig. Ihr eigentlicher Gegenstand (Verhalten OHNE pq-Backend bzw. never-raise von
witness_quorum) bleibt erreichbar, weil die ID-Berechnung reines SHA-256 ist und kein Backend
braucht. Also korrekte ID statt Nullen, Zweck unveraendert.

WAS DIE GEGENLESUNG SONST ERGAB: der erste Durchgang endete REJECT mit einem Hauptbefund an
`kid_v` — das in MEINEM Auszug nicht definiert war. un hat das ausdruecklich benannt ("nicht
definiert im Snippet, ich nehme an...") statt durchzuwinken. Mit nachgereichtem Vertrag fiel der
Befund (die Logik ist korrektes fail-closed), und `signer_present` bekam ausdruecklich PASS: die
Platzierung VOR verify_ed25519 ist fuer die beabsichtigte Semantik richtig. Der Eingabefehler lag
bei mir; die Klasse dazu steht im Gedaechtnis.

AUCH IM SELBEN DURCHGANG: `verify_witnessed_checkpoint` reichte `signer_present` nicht durch,
waehrend der tlogproof-Pfad es tut. Beim Lesen des eigenen Diffs gefunden, nicht von einem Test.

Batterie: 2143 passed, 9 skipped, 3 xfailed, 194 subtests, ruff clean, mypy clean.

Kein Push, kein PR, kein Merge, kein Tag.
…llen gegen meine

Zwei Dateien kollidierten, beide weil #141/#142 dieselbe Arbeit schon gelandet hatten, waehrend ich
sie lokal noch einmal machte. Aufgeloest zugunsten von main, nicht aus Hoeflichkeit, sondern
gemessen:

1. `_ACCEPTED`: ich hatte `OSError` aufgenommen. main nahm nur `FileNotFoundError` — und ihr
   Kommentar haelt fest, dass der ERSTE Versuch dort ebenfalls `OSError` war und von einer
   Gegenlesung als REJECT gefangen wurde. `OSError` ist die Basisklasse von `PermissionError`,
   `TimeoutError`, `BrokenPipeError`; sie alle stillschweigend zu akzeptieren ist ein FAIL-OPEN auf
   genau der Achse, die diese Eigenschaft verteidigt. Ich habe denselben Fehler ein zweites Mal
   gemacht, mit derselben Selbstbegruendung. Mains Fassung uebernommen.

2. `emit.load_signer`: mains Typboden ist aequivalent zu meinem und besser dokumentiert.

WAS AUS MEINER ARBEIT BLEIBT, weil es main NICHT hat:
- `tests/test_load_signer_fd_hazard.py` belegt, dass der Deskriptor NICHT GELESEN wird (er ist nach
  dem Aufruf noch offen), nicht bloss dass etwas geworfen wurde. Gegen mains load_signer gruen.
- Die Unterpaket-Luecke: mains Deckungs-Waechter nutzt `_SRC.glob("*.py")`, also nur die oberste
  Ebene. Gemessen auf dem zusammengefuehrten Baum: 90 Flaechen in 40 Modulen, und
  `experimental.enclave` ist NICHT dabei — obwohl es ausgeliefert wird, dokumentierter Importpfad
  und CLI-Unterbefehl ist. Folgt als eigener Commit.

Batterie auf dem zusammengefuehrten Baum: 2161 passed, 9 skipped, 3 xfailed, 228 subtests, ruff
clean. Der eine rote Test bleibt der fehlende Vor-Tag-Eintrag.
…ine Ebene tiefer

DIE FORM DES BEFUNDS IST DIE INTERESSANTE. `test_never_raise_population_guard.py` existiert, um zu
beweisen, dass keine Flaeche ausserhalb der Population liegt. Sein eigener Wahrheitsbegriff war
`_SRC.glob("*.py")` — nur die oberste Ebene. Ein Modul in einem UNTERPAKET lag damit ausserhalb der
Sollmenge und deshalb ausserhalb des Waechters, der beweist, dass nichts ausserhalb liegt. Der
Waechter gegen eine unvollstaendige Population war selbst unvollstaendig, eine Ebene tiefer.

GEMESSEN auf dem zusammengefuehrten Baum: glob 50 Module, rglob 56. Die sechs sind die fuenf
`adapters.*` und `experimental.enclave`. Von den sechs traegt GENAU EINES eine passende Flaeche
(`experimental.enclave.verify_enclave_attestation`) — die ehrliche Groesse der Luecke ist also EIN
Mitglied, nicht sechs. Es wird ausgeliefert, hat einen dokumentierten Importpfad und ist ein eigener
CLI-Unterbefehl (`verify-enclave`).

BEWEIS IN BEIDE RICHTUNGEN: nach der Erweiterung MELDETE der Waechter die Luecke von selbst
(`{'experimental.enclave': ['verify_enclave_attestation']}`), und erst das Eintragen in `_MODULES`
machte ihn gruen. Er hat die Luecke also gesehen, nicht ich sie ihm erzaehlt. Population jetzt 91
Flaechen in 41 Modulen (vorher 90/40), null Verstoesse.

DATEISYSTEM STATT `pkgutil.walk_packages`, und die Begruendung ist gemessen statt zitiert:
walk_packages muss jedes Paket IMPORTIEREN, um `__path__` zu lesen, und zieht damit Nebenwirkungen
(die experimental-Vorschauwarnung) in einen Entdeckungsschritt, der keine haben sollte. Der
dokumentierte veraenderliche Standard-Memo (CPython #127318) REPRODUZIERT hier NICHT — drei Laeufe,
je 62 Module — und wird deshalb als Grund fuer den einfacheren Weg genannt, nicht als Fehler, den
wir hatten. `rglob` liest Namen von der Platte: keine Importe, kein Memo, keine Nebenwirkungen, und
es bleibt in der Form, die diese Datei ohnehin benutzte.

Batterie: 2161 passed, 9 skipped, 3 xfailed, 228 subtests, ruff clean.
…euert wie entworfen

Owner-GO im Chat: "owner go erteilt fuer den merge sorge fuer fortschritt und wir inkludieren alles
jetzt best moeglich in das v3.8.0 release". #139 auf main gemergt (518d1ee), main hier
zusammengefuehrt.

DIE LATTE HAT GETAN, WOFUER SIE GEBAUT WURDE. Gestern trugen P1, P2 und P3 in
`test_pre_tag_gate_eigenschaften.py` ein `expectedFailure` mit der Begruendung: "sobald das Tor sie
schliesst, meldet unittest UNEXPECTED SUCCESS und zwingt zum Entfernen der Markierung". Genau das
ist eingetreten — mit #139s neuem Tor meldete die Suite drei Unexpected success, also drei
FEHLSCHLAEGE. Markierungen entfernt, die drei Eigenschaften sind jetzt normale Zusicherungen. Der
Absatz bleibt im Docstring stehen, weil er der Beleg ist, dass die Bauform funktioniert: ein `skip`
haette geschwiegen, als die Arbeit getan war.

CHANGELOG ZUSAMMENGEFUEHRT OHNE VERLUST. Mains `[Unreleased]`-Rubrik trug vier Stichpunkte, die
meine Fassung nicht hatte (Strukturbudget auf dem direct-dict-Pfad, zurueckgenommene
merkle_path-Kappe, typisierte Fehler auf zwei Pfadargumenten, plus den Banner). GEMESSEN, dass der
zugehoerige Code in DIESEM Baum liegt (BundleFormatError in evalcard und prereg vorhanden,
json_nodes im Budget) — er wird also mit 3.8.0 ausgeliefert und ist nicht "unveroeffentlicht". Die
Rubrik-Ueberschrift entfaellt deshalb, der Wortlaut ist unveraendert uebernommen, mit sichtbarer
Herkunftsangabe und die ###-Rubriken am Ende des Abschnitts, damit sie nicht mit den gleichnamigen
dieses Release verwechselt werden.

STAND JETZT, ehrlich: zwei rote Tests bleiben, beide aus DEMSELBEN Grund und beide richtig.
`TestF7PreTagAudit::test_released_version_has_audit_record` und #139s eigener Gegentest
`ProsaEntscheidetNicht::test_gegenrichtung_das_echte_repo_besteht_weiterhin` verlangen die
kanonische Zeile `pre-tag-adversarial-audit: RUN | version=3.8.0` in der Akte. Sie steht dort NICHT
— und sie zu schreiben waere heute eine falsche Attestierung: gelaufen ist eine 3-Linsen-Runde auf
dem Origin-Bindungs-Increment (NORMAL 3L/3I), nicht der Tiefenlauf auf dem eingefrorenen
Release-Digest. Der Eintrag folgt, wenn der Lauf gelaufen ist, nicht vorher.

Batterie: 2232 passed, 9 skipped, 380 subtests.
…asse hatte mehr Mitglieder

DER TIEFENLAUF HAT SEIN ERSTES ZIEL GEKIPPT, und das ist der Sinn der Uebung.
`FINDING_erwartungsvergleich_klasse.md` wurde am 2026-08-16 als "7 von 7 Mitgliedern" geschlossen.
D1 fragte nicht "laufen die Korpus-Tests" (das bestaetigt nur), sondern "welche Vergleichsstellen
gibt es und welche ist ungedeckt". GEMESSEN: 14 Stellen an 10 Parametern, 8 gedeckt, DREI ueber
Zeichenketten ungedeckt. Die Klasse war ueber eine HANDGEPFLUECKTE Mitgliederliste geschlossen —
dieselbe Fehlerform wie bei der never-raise-Population einen Tag zuvor.

ZWEIMAL DIESELBE FORM IN ZWEI TAGEN heisst: das Problem ist nicht die einzelne Liste, sondern dass
eine Klasse ueberhaupt ueber eine Liste geschlossen wird. Deshalb kein drittes Handnachtragen,
sondern ein Waechter, der die Grundmenge aus dem BAUM ableitet
(`tests/test_erwartungsvergleich_population_guard.py`), und danach die drei Luecken:
  outcome.verify_outcome_receipt          expected_decision_ref
  experimental.enclave.verify_enclave_... expected_profile
  public_transparency.evaluate_public_... expected_root_b64
Jede war zuvor NUR gegen einen voellig fremden Wert geprueft — und ein fremder Wert faellt auch
unter startswith/casefold/strip durch, kann also nicht zeigen, dass der Vergleich exakt IST.

DIE AUSNAHME IST ABGELEITET, NICHT GEPFLEGT: `expected_tree_size: Optional[int]` faellt heraus, weil
seine ANNOTATION keine Zeichenkette zulaesst — ein Korpus ueber Gross/Klein und Leerzeichen hat auf
einer Zahl keinen Gegenstand. Ein Parameter ohne lesbare Annotation gilt als UNGEDECKT, nicht als
ausgenommen: im Zweifel fordern.

DREI EIGENE MESSFEHLER, alle VOR der Behauptung gefangen und im Waechter dokumentiert, damit sie
beim naechsten Umbau nicht still zurueckkommen:
 1. `expected_key_id(ident)` ist ein FUNKTIONSAUFRUF, kein Erwartungsparameter — der erste AST-Lauf
    zaehlte ihn mit und blies den Befund auf.
 2. Das Korpus-Muster suchte einzeilig und uebersah `test_kbjwt.py` (mehrzeilige Lambda-Form) —
    zwei gedeckte Parameter galten faelschlich als ungedeckt.
 3. Die Zeichenklasse `[a-z_]+` schloss ZIFFERN aus und uebersah `expected_root_b64`, den einzigen
    Parameter mit Ziffern im Namen. Das Muster stammte aus den Beispielen, die gerade vor mir lagen,
    und die waren alle ziffernlos.

RUECKNAHME-PROBE je Fix, mit vorher im Original nachgelesenen Ankern: decision_ref auf startswith
-> 2 rot; profile auf startswith -> 1 rot -> 3 rot; root_b64 auf strip -> 1 rot -> 4 rot;
Grundzustand kehrt zurueck. (Die "1 rot" der Kontrolle ist ein Artefakt des Probenaufbaus — eine
Testdatei fehlte im Wegwerf-Baum —, die Deltas sind eindeutig.)

Und der Index-Waechter fing die elfte Akten-Datei: "ten files" wurde falsch, sobald
PRE_REGISTRATION_DEEP_380.md dazukam. Genau dafuer ist er gebaut.

Batterie: 2241 passed, 9 skipped, 413 subtests, ruff clean. Die zwei roten bleiben die fehlende
Vor-Tag-Attestierung — sie folgt, wenn der Lauf durch ist, nicht vorher.
…gt, fuenf gehalten

Die kanonische Attestierung steht jetzt in audit_artifacts/380/DEEP_RUN_RECORD_380.md, und sie steht
dort, WEIL der Lauf stattgefunden hat — nicht damit die Suite gruen wird. Bis eben waren zwei Tests
rot, die genau diese Zeile verlangen, und sie waren aus dem richtigen Grund rot.

GEGENSTAND ZUERST, wie §9 es verlangt: benoteter Code src/-Baum 203644c, eingefroren bei e0208e3.
GEMESSEN, dass src/ seit dem Einfrieren UNVERAENDERT ist — nur tests/ ist gewachsen, durch die Funde
des Laufs selbst. Delta v3.7.0..e0208e3 unter src/: 14 Dateien, 489+/19-.

D1 WIDERLEGT, und das ist das eigentliche Ergebnis. Die Frage war nicht "laufen die Korpus-Tests"
(das bestaetigt nur), sondern "welche Vergleichsstellen gibt es und welche ist ungedeckt". Gemessen:
14 Stellen an 10 Parametern, 8 gedeckt, DREI Zeichenketten-Vergleiche ungedeckt. Der Befund war als
"7 von 7 Mitgliedern" geschlossen — die Mitgliederliste war handverlesen. Zweite Wiederholung
derselben Form in zwei Tagen, deshalb ein aus dem Baum abgeleiteter Waechter statt eines dritten
Handnachtrags.

D2 haelt (11 Ursachen, 11 verschiedene Ausgaben, Kontrolle zuerst; KEIN Beweis der Abwesenheit).
D3 haelt in beide Richtungen (gepflanzter raise im JUENGSTEN Mitglied experimental.enclave und im
   lange gedeckten statuslist, Grundzustand kehrt zurueck).
D4 haelt gegen die ECHTEN Dateien (11 gepruefte Dateien, keine erteilte, Detektor lebt).
D5 haelt in beide Richtungen (PermissionError wird NICHT verschluckt, FileNotFoundError schon —
   die eine bewusste Zulassung; meine eigene OSError-Fassung war ein fail-open und wurde im Merge
   durch mains verengte ersetzt).
D6 haelt, NACHDEM die Sonde zweimal korrigiert wurde: vier gemeldete Widersprueche waren ihre
   eigenen Falschtreffer ("N files" meinte den src/-Delta, nicht die Akte), und dieselbe Annahme
   steckte in einem heute ausgelieferten Waechter, wo sie nur zufaellig gutging. Dabei fiel ein
   zweiter Defekt heraus: die Wortzahl-Liste endete bei "ten", also hoerte der Waechter genau in dem
   Moment auf zu pruefen, als die Akte auf ELF Dateien wuchs und ich den Satz korrigierte.

WAS DIE ATTESTIERUNG NICHT BEHAUPTET, im Record ausgeschrieben: nicht "der Code ist korrekt"
(bounded search, Grenze benannt), nicht "released" (Tag und Publish bleiben Owner-Akte), und
ausdruecklich nicht "sechs unabhaengige Gegenleser" — der Lauf wurde von EINEM Agenten sequentiell
gefahren, was schwaecher ist als die Methodik annimmt, und genau deshalb endet jedes Ziel in einer
ausfuehrbaren Probe mit Kontrolle.

Batterie: 2243 passed, 9 skipped, 420 subtests, 0 failed — zum ersten Mal vollstaendig gruen.
…nen Waechter

un las den Populations-Waechter gegen und sagte REJECT. Drei der vier Punkte sind echt, einer nicht
— und der erste hat sich am eigenen Baum bestaetigen lassen.

PHANTOM-TREFFER, gemessen: die Deckungspruefung nahm ein 400-Zeichen-TEXTFENSTER ab jedem
`pruefe_exakt(`-Vorkommen. Am eigenen Baum fuehrte sie damit `expected_x` als gedeckt — ein Name,
der NUR in einem Docstring-BEISPIEL dieses Moduls steht. Haette je ein echter Parameter so geheissen,
waere er still als gedeckt durchgegangen und die Klasse faelschlich geschlossen. Der Vergleich beider
Fassungen ist eindeutig: Fenster 13 Namen, AST 12, Differenz genau dieser eine.

Eine Fenstergroesse ist eine geratene Konstante ueber einer Groesse, die niemand beschraenkt
(Formatierung). Der AST kennt die Aufrufgrenze exakt: gesucht werden jetzt Schluesselwort-Argumente
`expected_*` INNERHALB des Aufrufknotens, Lambdas eingeschlossen. Prosa kommt dort nicht vor. Eine
nicht parsbare Testdatei traegt NICHTS bei, statt per Text zu raten — eine kaputte Datei soll Deckung
nicht vortaeuschen.

ZWEITER PUNKT: `"str" in annotation` traf auch `abstract`, `struct` und jeden Typnamen mit `str`
darin. Jetzt `\bstr\b`. Die Richtung des Fehlers war fordernd (ein Nicht-String haette ein Korpus
gebraucht, das dort keinen Gegenstand hat) — falsch bleibt falsch.

GATE-META-TEST fuer beide Fehlerrichtungen, die un benannte: ein Name aus einem Docstring darf NICHT
als gedeckt gelten, und zwei benachbarte Aufrufe duerfen ihre Deckung nicht aneinander vererben.
Beides an einem Wegwerf-Baum gemessen, mit der Gegenrichtung (echte Aufrufe MUESSEN gefunden werden)
im selben Test.

NICHT UEBERNOMMEN, mit Begruendung: uns Punkt zu `ast.Attribute`/`ast.Subscript`
(`obj.expected_x == y`) trifft nicht — das sind keine PARAMETER, und der Waechter misst ausdruecklich
Parameter; ausserdem rekursiert `ast.walk` ohnehin in Aufrufe hinein, was un im selben Absatz selbst
korrigiert.

Batterie: 2244 passed, 9 skipped, 420 subtests, ruff clean.
The mutation gate reported two gaps on 518d1ee and blocked v3.8.0:

  GAP  [relation: cycle detection disabled] SURVIVED (red=1)
  GAP  [relation: verified-flag laxened] SURVIVED (red=1)

NEITHER IS A COVERAGE HOLE. Both are the same stale-operator shape, and the cause
is a fix that was right.

#139 made the ancestor walker apply the gates the direct-edge arm already had (L4-01:
the gate was distance-scoped, and the distance is attacker-chosen). It also added a
look-ahead so a back-edge is found before descending, making the cycle code independent
of sibling order. Both were deliberate defence in depth.

The side effect: each operator disables ONE line where the property now rests on TWO.
The surviving guard catches the vector, the mutant lives, and the gate cries gap where
the defence holds. An operator whose label says "disabled" must actually disable --
otherwise the gate measures the wrong thing in the SAFE direction, and that is how a
gate gets ignored.

MEASURED, each direction separately, on an extracted tree with the release venv:

  cycle, line 367 alone            -> suite green   (look-ahead still catches)
  cycle, look-ahead alone          -> suite green   (line 367 still catches)
  cycle, BOTH                      -> 4 red, among them
                                      test_injected_back_edge_onto_path_is_caught_or_unreachable
  verified, direct arm alone       -> suite green   (walker catches one level down)
  verified, BOTH                   -> red, tests/test_relation_profile.py::
                                      TestVerifyRelationshipEdges::test_verified_flag_must_be_exactly_true

So the killing tests EXIST and always did. They simply cannot kill a half-disabled
property.

THE FIX is in the operator table, not in the source: an operator may now name several
sites (old/new as tuples). Single-string operators are unchanged, so the other 86 keep
their exact meaning. Verified with the gate's own machinery on a filtered set: both now
report KILLED (red=4 and red=20), 0 gaps.

ALSO ADDED, and honestly not required for the above: three truthy-but-not-True vectors
(1, "true", ["ja"]) in the distance-invariance generator. The file's own header says
every chain test hardcodes "verified": True, so the strictness of `is not True` was
untested ACROSS HOP DISTANCES. test_verified_flag_must_be_exactly_true already covers
the direct arm; these extend the property to every hop, which is what this file is for.
They raise the mutant's red count from 2 to 20 but are not what makes it die.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@b7n0de
b7n0de merged commit 7b35e57 into main Aug 17, 2026
22 checks passed
@b7n0de
b7n0de deleted the release/v3.8.0 branch August 17, 2026 04:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant