Skip to content

Receipt envelope profile v0.1, mutation operator freshness gate, and the C12.1 scope correction - #159

Merged
b7n0de merged 20 commits into
mainfrom
feat/umschlagprofil-und-zwei-rote-gates-20260830
Aug 30, 2026
Merged

Receipt envelope profile v0.1, mutation operator freshness gate, and the C12.1 scope correction#159
b7n0de merged 20 commits into
mainfrom
feat/umschlagprofil-und-zwei-rote-gates-20260830

Conversation

@b7n0de

@b7n0de b7n0de commented Aug 30, 2026

Copy link
Copy Markdown
Owner

What & why

Adds a documented receipt envelope profile (proposed, not adopted) together with
executable counter-probes for it, closes the mutation-operator gap that the
mutation gate could not previously see, records why audit-candidate-matrix
is red on a working branch by design, and maps the profile against the two SCITT
documents it sits next to.

No issue is linked. This is maintainer work.

Size, measured against bd0161ab..3e9c5542: 20 commits, 39 changed files.

The four parts

  1. Receipt envelope profile v0.1. A written profile for a shared receipt
    envelope, with rules R1 to R6. It is a proposal. Nothing in this repository
    treats it as adopted, and PRIORITY_RECORD.md states that explicitly.

  2. The profile now satisfies its own R6, for R1 to R4. R6 requires every rule
    to ship an executable counter-probe plus a positive control. Ten vectors, nine
    effective planted defects, nine caught. R5 explicitly ships no counter-probe,
    because its field form was withdrawn (see below) and a counter-probe against a
    withdrawn form would test nothing.

  3. mutation_operators_fresh.py, a pre-check — a script, not a wired gate.
    The existing mutation gate can report a clean run while an operator silently
    matches nothing, because operators quote source text verbatim and a legitimate
    edit to the quoted line makes the operator measure nothing. Both gaps in the
    30 August run were exactly that, and both were triggered by a class fix. The
    script asserts that all 88 operators find their pattern, exactly once each.
    It is not referenced by any workflow, on this branch or on main, and this
    PR changes no file under .github/workflows/.
    Wiring it is separate work.

  4. The relationship to RFC 9943 and to draft-mih-sokolov-scitt-payload-binding-02.
    docs/SCITT_CPB_MAPPING.md maps our envelope clause by clause against that
    draft: where the behaviour agrees, where it diverges, and where a claim in the
    draft did not survive checking at source. INTEROP.md adds a measurement record
    against a neighbouring implementation, pinned to three commit hashes and dates.
    Both are measurements, not assessments; the record says so in its own text and
    states what it does not measure.

What was withdrawn before this PR opened

An earlier commit on this branch proposed three coverage fields
(population_size, evaluated_count, unresolved_count). They were removed
again, because draft-hillier-coverage-attestation-00 (20 August 2026) normatively
refuses exactly that shape: a remainder that reconciles only by subtraction must be
refused. The commits are still in this branch's history; the tip does not carry the
fields, and the schema property set is name-identical with main.

The C12.1 scope correction

audit-candidate-matrix is red on this branch on purpose, and the reasoning now
lives in the docstring of c12_1_pretag_audit rather than only in a commit message.
A receipt binds a subject_tree_digest. A working branch is not a release, and its
tree changes on merge. Producing a pre-tag receipt here would reproduce the very
finding C12.1 exists to catch. The check is doing its job against an object it should
not bless.

Measured on the branch head: the run reports 33 checks, PASS 30, PENDING 0, DATA_BLOCKED 1, EXTERNAL 1, FAIL 1. The single FAIL is C12.1. The other two
non-PASS entries are declared states, not failures: C6.3 (the 24h soak artifact
records 90s) and EXT.1 (the deliberately open external human audit).

The hermetic-cleanroom repair

hermetic-cleanroom first failed on this branch (1 failed, 2379 passed): a test added here reads
docs/RECEIPT_ENVELOPE_PROFILE.md, which the sdist prunes, and conftest.py's derivation could not
see it — the module binds its root to a lowercase local (root = Path(__file__)...), and the path
sits behind a loop variable.

This fixes the class, not the instance. The change is in the derivation rule itself, not in the
explicit fallback list: that list is documented as the fallback for tests whose repo dependency is
not a path literal in their own module, and this one is. Widening the uppercase name allowlist was
measured and rejected — it would bind root = self._copy_corpus() and repo = tmp_path / "r" as
roots (14 false-positive paths in two modules, silently skipped outside a checkout). Instead a root
is now recognised by its origin: derived from __file__ with a parent-step count that matches this
module's distance to the repo root, so Path(__file__).parent (the tests directory, written in 31
places under tests/test_*.py, 33 across tests/) does not bind. Loops over a tuple of string
constants resolve to those values, scoped to the loop body, and a variable reassigned inside the body
does not bind.

Does it change what the collection decides to run? In a checkout, no; in an sdist, yes — and that
is the point.
Measured after the change: zero modules count as repo-context in a full checkout, so
nothing new is skipped there. In an sdist-like tree without docs/, the target module now correctly
counts as repo-context and is skipped instead of failing. The target module yields 5 paths that all
exist. Six tests pin the individual properties.

Scope

  • The change stays within the scope of the maintainer task it implements
  • No release plumbing changed. This PR touches no file under .github/workflows/
    (measured: 0 of 39 changed files), and adds no new CI gate. The pre-check in part 3
    ships as a script only
  • No new binary/blob/large fixture files (measured: 0 binary blocks in the diff)

Checks

  • ruff check . and mypy src pass locally (65 source files, no issues)
  • Tests pass: 2593 passed, 8 skipped, 735 subtests; conformance 77/77
  • The library keeps its core promise. The profile proves envelope shape and
    binding, deliberately not that any number inside is true

Notes for the maintainer

Measured locally against the branch head, not in CI: mutation_operators_fresh.py,
test, conformance, ruff, mypy, claims-hygiene, doc-link-check,
mutant-signature-guard, branch-base-check. CI is the first run for CodeQL,
Scorecard, demo-reproducible, published-artifact-gate, release-integrity,
fork-pr-isolation, dependency-audit, rust-parity, coverage and anchors.

The measurement that was open when this PR was written has since completed. The
canonical serial mutation_check.py run finished with => OK (88 operators, 0 gap(s)),
exit 0, and a single declared-expected survivor
([cosign: blob length exact -> lax (EQUIVALENT)] SURVIVED (red=1) expected). The
parallel run had reported the same 88/0 earlier; those remain two different
measurements, and the agreement between them is only now a fact rather than an
assumption. In CI, mutation passed in 3h12m33s.

Known and deliberately not fixed here: the signed audit_output_digest in the
v5.0.0 receipt resolves to no findable artifact and nothing checks it. Tracked
separately. The v5.0.0 receipt itself is valid and binds the tag tree.

kraxo added 20 commits August 30, 2026 13:41
…T-Entwurf, plus die Abbildung Klausel fuer Klausel

Phase 2 und 2b des Auftrags QITEM-UMSCHLAGPROFIL-UND-ROTE-GATES-01 (Fassung 7, Digest
846d5e86c4cb5b8f gegen die Datei gerechnet und gleich).

docs/RECEIPT_ENVELOPE_PROFILE.md, 174 Zeilen
  Kennung proofbundle/receipt-envelope-profile/v0.1, AUSDRUECKLICH als Owner-Entscheidung markiert:
  vorgeschlagen, nicht uebernommen. Der Positionswechsel steht im Kopf — ein Profil ZU einem
  Standard, nicht ein Format daneben. R1 bis R4 gegen den CPB-Entwurf mit Verweis, R5 und R6 als
  unsere Ergaenzung, weil dessen Abschnitt 1.1 Abdeckung und Schluesselvertrauen ausdruecklich nicht
  behandelt.

  JE REGEL EINE HERKUNFTSZEILE, aus den Mac-Quellen GELESEN und nicht aus dem Auftrag abgeschrieben.
  Die R1-Zeile traegt die Selbstkorrektur vom 28.08. mit: der erste Durchgang meldete "no finding"
  und mass den falschen Gegenstand (jcs.canonicalize statt des Alias, den Signieren und Pruefen
  wirklich fahren). Die Divergenz ist allein das String-Escaping, ensure_ascii=True gegen rohes
  UTF-8; Reihenfolge und Zahlen sind RFC-8785-korrekt, die UTF-16-Schluesselordnung haelt.
  ASCII-Quittungen bleiben identisch, {"café":1} nicht.

  R6 STEHT EHRLICH GEGEN UNS: die Herkunftszeile sagt, dass die Regel KEINE externe Quelle hat, und
  haelt fest, dass unser eigenes mutation-Gate zum Zeitpunkt des Schreibens nicht gruen war — also
  genau der Zustand, den R6 als nicht erfuellt definiert. (Der Lauf ist noch nicht durch; das
  Ergebnis wird in Phase 1 gemessen und nachgetragen.)

  Der neue Nichtziel-Absatz im Wortlaut, als vorzulegen gekennzeichnet: das Profil schafft keine
  pruefende Instanz, Konformitaet wird vorgefuehrt und nicht verliehen. Die Ankerneutralitaet steht
  unveraendert als rote Linie daneben, ausdruecklich auch gegen unseren eigenen Anker.

  Zitierblock ohne DOI (kein Platzhalter, keine erfundene Nummer) und die Versionsregel in einem Satz.

docs/SCITT_CPB_MAPPING.md, 92 Zeilen
  G1 bis G4, je Fundstelle im eigenen Quelltext neben der Klausel des Entwurfs. ALLE VIER VOR DEM
  SCHREIBEN AM ARBEITSZWEIG NACHGEMESSEN:
    G1  canonical.py:43  CONTENT_ROOT_ALG = "jcs-sha256-v1"   Verhalten gleich, Token anders
    G2  bundle.py:770    merkle.leaf_hash(payload)            harte Abweichung gegen CPB 7.1
    G3  eval_claim_v0_1.schema.json:105                       Algorithmus im Schluesselnamen
    G4  alle NEUN Schemata                                    null Treffer fuer alle drei Felder

  ZWEI ERGAENZUNGEN, die in der Lueckenanalyse so nicht stehen: der Riegel fuer G1 EXISTIERT bereits
  (intoto.py:145 fuehrt legacy-sortkeys-json-v0 als eigenen Token, intoto.py:178 WEIST einen
  sort_keys-Koerper ZURUECK, der als jcs-sha256-v1 angeboten wird) — es fehlt nicht die Deklaration,
  sondern die Umstellung, und die ist owner-gated. Dazu ein eigener Abschnitt "What is NOT measured".

DIE DREI VERBOTE EINGEHALTEN: die Merkle-Blattkonstruktion wird DEKLARIERT statt umgebaut (ein Umbau
wuerde jeden bereits ausgestellten Beleg entwerten), der Token wird ABGEBILDET statt umbenannt, die
intoto-Exportpfade bleiben unangetastet — dafuer der verlangte eigene Befund
INTOTO-EXPORTPFADE-AUF-ECHTES-JCS-OWNER-GATED-SEMVER-01 im 2bedone-Befundstrom.

Beide Seiten enthalten keine Wertung ueber die Gegenseite. Was nicht gemessen ist, steht als NOT
MEASURED da und wird nicht gefuellt.

Arbeitszweig feat/umschlagprofil-und-zwei-rote-gates-20260830 auf origin/main bd0161a.
Nichts auf main, kein Tag, kein Release, kein Beitrag in einem fremden Issue.

Owner-GO: GO_OWNER_UMSCHLAGPROFIL_PHASE2_NO_RELEASE
…zwei Regeln, die wir von anderen verlangt und selbst nicht gefuehrt haben

Phase 2c des Auftrags QITEM-UMSCHLAGPROFIL-UND-ROTE-GATES-01 (Fassung 7). Schliesst G4 und G3
der Lueckenanalyse. Beides ADDITIV: nichts Bestehendes wird pflichtig, keine Version erzwungen.

DIE MESSUNG VORWEG, die den Entwurf bestimmt hat
  Die JSON-Schemata sind DOKU, nicht Durchsetzung — jsonschema ist Test-/Dev-Abhaengigkeit, erzwungen
  wird in handgerollten Modulen. Jede Ergaenzung braucht deshalb BEIDE Traeger, sonst driften
  Beschreibung und Verhalten auseinander. Genau dafuer existiert tests/test_schema_parity.py, und
  genau diese Deckung fehlte fuer eval_claim. Sie ist jetzt da.

G4, Abdeckung (R5) — gemessen NULL von NEUN Schemata trugen eines der drei Felder
  schemas/eval_claim_v0_1.schema.json: optionaler coverage-Block, optional als GANZES und
  VOLLSTAENDIG wenn vorhanden. Ein fehlender Nenner laedt den Leser ein, das Verhaeltnis fuer 1 zu
  halten, deshalb kein Teil-Block.

  ZWEI ENTSCHEIDUNGEN, die ich benenne statt sie zu verstecken:
  (1) evaluated_count MUSS dem n des Belegs gleichen. n ist BEREITS die Menge, ueber die der Aggregat
      gerechnet wurde (intoto.py:426 exportiert es als sampleSize) — eine zweite freie Zahl waere
      eine zweite Wahrheit ueber dieselbe Groesse. Die Bindung spiegelt die vorhandene Regel
      samples.n == n samt ihrer Begruendung.
  (2) Die drei Zahlen sind DISJUNKT: evaluated + unresolved <= population, der Rest ist die bewusst
      ausgeschlossene Menge. Ohne diese Festlegung waere "unresolved" mal Teilmenge, mal Nachbar.

  Erzwungen auf ALLEN DREI Pfaden — bauen, ausgeben, pruefen — aus EINER Definition
  (_coverage_error). Eine nur am Emit-Pfad erzwungene Garantie wird von einem handsignierten Beleg
  umgangen; das ist die emit-vs-verify-Asymmetrie, die evalclaim.py fuer samples und assurance_level
  bereits bewacht und im Quelltext ausdruecklich lehrt. Am Emit zusaetzlich, weil einen Beleg
  auszugeben, den der EIGENE Verifier zurueckweist, eine Falle fuer den Aufrufer ist.

G3, typisierte Digest-Referenz — und hier korrigiere ich mein eigenes Dokument von heute frueh
  Der erste Durchgang mass eval_claim_v0_1.schema.json:105 und verallgemeinerte. Falsch: die
  decision-receipt fuehrt BEIDE Formen nebeneinander — sha256Digest (Algorithmus im Schluesselnamen)
  UND relationDigest (digestAlgorithm als eigenes Pflichtfeld), letzteres mit exakt der Begruendung
  des Entwurfs im eigenen Text: "a missing value is exactly where algorithm confusion hides". Zwei
  der vier Felder lagen ohnehin am evidenceRefs-Eintrag (relation, predicateType).
  DIE LUECKE WAR NIE ABWESENHEIT, SIE WAR INNERE UNEINHEITLICHKEIT.

  Neu: $defs/typedDigest + optionales typedDigest an evidenceRefs[]. Pflicht type/digestAlgorithm/
  digest, optional purpose. Ersetzt nichts — digest bleibt erforderlich, ein Eintrag darf digest
  allein, typedDigest allein oder beides tragen. NAMENSENTSCHEID: der Entwurf schreibt digest_alg,
  wir schreiben digestAlgorithm, weil das Feld in relationDigest mit derselben Bedeutung schon
  existiert und die Datei durchgehend lowerCamelCase ist (ITE-9). Zwei Namen fuer eine Groesse in
  EINER Datei waeren die naechste Drift; die Entsprechung steht stattdessen in der Abbildung —
  dieselbe Frage wie bei G1. type und purpose sind die Namen des Entwurfs unveraendert.

GEMESSENE GRENZE, als ausfuehrbare Tatsache statt als Behauptung
  additionalProperties:false heisst, dass "additiv" nur in EINE Richtung gilt. Gemessen: ein alter
  Beleg gilt unter dem neuen Schema; ein neuer Beleg mit coverage faellt unter dem alten durch — und
  zwar als UNGUELTIG, nicht als unbekannt. Das ist genau die Unterscheidung, die R2 unseres eigenen
  Profils von einem Verifier verlangt, und unsere Schemaform leistet sie hier nicht. Festgehalten in
  test_gemessene_grenze_additiv_ist_nur_eine_richtung.

ANTI-TAUTOLOGIE, weil gruen nichts beweist
  Zwoelf gepflanzte Defekte, ALLE zwoelf gefangen. coverage 6/6 (Verify-Pfad entschaerft, n-Bindung
  weg, Disjunktheit weg, bool/negativ weg, Vollstaendigkeit zu Teilmenge, Emit-Pfad entschaerft);
  typedDigest 6/6, darunter ZWEI schema-seitige — die Paritaetsbehauptung bindet also wirklich beide
  Traeger und nicht nur den Validator. Beide Dateien danach byte-identisch wiederhergestellt.

Tests 30 neu (14 + 16), ruff clean, mypy clean.
Dokumente nachgezogen: G3-Korrektur und G4-Fortschreibung in SCITT_CPB_MAPPING.md, R3-Zeile und
R5-Nachtrag im Profil — samt der ehrlichen Grenze, dass die Zahlen ausstellerseitig ERKLAERT sind:
die Signatur macht sie faelschungsevident und zurechenbar, nicht richtig.

DIE DREI VERBOTE EINGEHALTEN: Blattkonstruktion unangetastet (G2 bleibt deklariert), Token
jcs-sha256-v1 unveraendert, intoto-Exportpfade nicht umgestellt.

Owner-GO: GO_OWNER_UMSCHLAGPROFIL_PHASE2_NO_RELEASE

Gemessen, nicht behauptet: volle Suite 2590 passed, 8 skipped, 738 subtests, exit 0 (317 s).
Nachtrag im selben Zug: _typed_digest_error prueft seinen Schluesselsatz jetzt SELBST. Er wird
direkt aufgerufen und direkt geprueft, also muss er allein tragen — sich auf den verschachtelten
Verschluss des Aufrufers zu verlassen machte das "EINE Definition" der Dokzeile nur in einem
Codepfad wahr. _coverage_error tat es von Anfang an; die Asymmetrie zwischen zwei am selben Tag
geschriebenen Helfern waere die naechste Drift gewesen.
…tte Regel, die wir gefordert und nicht gefuehrt haben

Phase 3 des Auftrags QITEM-UMSCHLAGPROFIL-UND-ROTE-GATES-01 (Fassung 7): neue Vektorfamilie fuer
R1-R5 in der BESTEHENDEN Konformitaetsstrecke, je Regel Gegenprobe UND Positivkontrolle, alle durch
den EIGENEN Emit-/Verify-Pfad.

DIE MESSUNG, DIE DEN ABEND BESTIMMT HAT
  Bevor ich Vektoren fuer R2 schreiben konnte, musste ich R2 gegen den eigenen Pfad messen. Ergebnis:
  decode_eval_claim gibt fuer eine FREMDE schema-id None und fuer einen UNGUELTIGEN Beleg ebenfalls
  None. Zwei Zustaende, die R2 ausdruecklich trennt, sind am Rueckgabewert nicht unterscheidbar —
  derselbe Mangel, den wir am 26.08. an inspect-receipts@397ae3ad beanstandet haben, nur mit
  umgekehrtem Vorzeichen. Nach R5 (2c) und R6 waere das die DRITTE Regel gewesen, die wir von
  anderen verlangen und selbst nicht fuehren.

  ADDITIV GESCHLOSSEN: classify_eval_claim, drei Ausgaenge (valid / refused_unknown_schema /
  invalid), NEUE Funktion. decode_eval_claim ist UNVERAENDERT — sein Vertrag ist veroeffentlicht,
  Aufrufer pruefen auf 'is None', und eine Umstellung waere ein brechender SemVer-Schritt und damit
  Owner-Sache. Als Befund festgehalten:
  DECODE-EVAL-CLAIM-KOLLABIERT-ABLEHNUNG-UND-UNGUELTIG-01 (deklariert_offen).

  DIE REIHENFOLGE IST TEIL DER REGEL: Echtheit wird ZUERST entschieden. Eine kaputte Signatur IST
  beurteilbar; "ich kann nicht urteilen" waere dort die schwaechere, falsche Antwort und liesse einen
  Faelscher Schweigen erkaufen, indem er das Schema-Feld umbenennt.

DIE FAMILIE
  conformance/envelope_profile/, elf Faelle, neue kind envelope_profile_rule (additiv im
  vector_schema-Enum, fail-closed bei unbekannter kind wie alle anderen). Der Fall deklariert GENAU
  EINE Erwartungsachse — contentRootHex / nonConformantDiffers / classification /
  coverageDistinguishable; ein Fall, der keine deklariert, ist ein FAIL, kein Skip. Ein
  unterdeklarierter Fall ist der leise Weg, auf dem ein Korpus Faelle bekommt, die nicht durchfallen
  koennen. Gegengeprueft: die Vorbedingung der Korpus-Integritaet faengt genau das.

  Die POSITIVKONTROLLEN sind keine Zierde. Ohne sie bekaeme ein Verifier, der ALLES ablehnt, die
  volle Punktzahl auf den Gegenproben, und der Korpus nennte das Konformitaet.

  R6 hat mit Absicht KEINEN eigenen Vektor: ein Fall, der behauptet, dass die Faelle existieren,
  waere genau die Tautologie, vor der die Regel warnt.

DER META-TEST HAT ZWEI ECHTE LUECKEN GEFUNDEN, und das ist der Ertrag dieser Runde
  Erster Durchgang: zehn Vektoren, alle gruen, 7 gepflanzte Defekte — zwei ENTKAMEN.
  · M2 (Echtheit nicht mehr zuerst) blieb unentdeckt: kein Vektor war zugleich unpruefbar UND
    fremd-schematisch. Mein erster Reparaturversuch verfaelschte die NUTZLAST, womit der Beleg ueber
    einen ANDEREN Weg auf invalid faellt — der Vektor unterschied weiterhin nichts. Erst die
    Verfaelschung der SIGNATUR bei wohlgeformter Nutzlast trifft die Kante.
  · M5 (coverage-Pruefung am Verify-Pfad entschaerft) blieb unentdeckt: die Unit-Tests fingen es, der
    Korpus nicht — und der Korpus ist die AEUSSERE Autoritaet.
  Zweiter Durchgang nach beiden Ergaenzungen: 7 von 7 GEFANGEN. Die von R6 verlangte Erkennungsrate
  ist damit gemessen, nicht behauptet.

  Beide neuen Vektoren tragen im rationale, WARUM sie existieren — dass ein gepflanzter Defekt sie
  erzwungen hat und nicht das Schreiben der Vektoren. Eine dokumentierte, unbewiesene Eigenschaft ist
  genau die Klasse, die dieser Korpus finden soll.

R1 ist bewusst nicht-ASCII
  {"café":1,...} — Escaping ist die EINZIGE Achse, auf der die beiden Serialisierungen divergieren.
  Reihenfolge und Zahlen sind bei beiden RFC-8785-korrekt. Genau deshalb sah ein Korpus aus drei
  ASCII-Vektoren am 26.08. "no finding", und genau deshalb steht hier ein Umlaut.

Konformitaet: 79/79 (vorher 67, +11 neu +1 aus dem Meta-Test).
Tests: 8 neu (tests/test_classify_eval_claim.py), darunter die Deckungspruefung, dass JEDE Regel
R1-R5 eine Gegenprobe UND eine Positivkontrolle fuehrt. ruff clean, mypy clean.
Dokumente nachgezogen: R2- und R6-Nachtrag im Profil, neuer Abschnitt in CONFORMANCE.md.

DIE DREI VERBOTE EINGEHALTEN: Blattkonstruktion unangetastet, Token jcs-sha256-v1 unveraendert,
intoto-Exportpfade nicht umgestellt.

Owner-GO: GO_OWNER_UMSCHLAGPROFIL_PHASE2_NO_RELEASE

Volle Suite gemessen: 2599 passed, 8 skipped, 738 subtests (295 s). Konformitaet 79/79.
…CORD — als Messprotokoll, nicht als Urteil

Phase 4 des Auftrags QITEM-UMSCHLAGPROFIL-UND-ROTE-GATES-01 (Fassung 7).

INTEROP.md, neuer Abschnitt zu CSOAI inspect-receipts
  Der erste Nachbar in dieser Datei, der UNABHAENGIG gemessen wurde statt aus seiner Doku gelesen —
  die ValiChord-Zeilen sagen das ausdruecklich von sich, hier ist es umgekehrt, und der Abschnitt
  sagt das auch.

  Drei gepinnte Staende: ca3fd060 (19.08.), 8e33f160 (20.08.), 397ae3ad (29.08.).

  UMSCHLAG-INTEROP, beide Richtungen scheitern VOR der Signaturpruefung, mit den genauen
  Abbruchstellen: ihr Beleg an proofbundle/bundle.py:298 (UnsupportedError, unbekanntes Schema),
  unser valid-minimal an ihrem receipt.py:295-301 (content_id mismatch — unser Bundle traegt gar kein
  content_id). Zwei Implementierungen, die beide richtig signieren, koennen einander nicht lesen.
  Genau das ist der Gegenstand von Issue 147.

  Die KONTROLLE steht mit im Text, samt des eigenen Messfehlers: der erste Durchgang rief unsere CLI
  als `python -m proofbundle` auf, was das Paket nicht anbietet — beide Beine scheiterten an der
  Aufrufform statt an der Sache, und ausgerechnet das Kontrollbein war das wichtige. Ein Fehlschlag
  am Wrapper sieht aus wie ein Fehlschlag an der Sache.

  KANONISIERUNG: die Schluesselordnung stimmt UEBERALL — das war die eigentliche Frage von Vektor 1
  und sie ist beantwortet. Die Divergenz ist allein das Escaping (ensure_ascii=True gegen rohes
  UTF-8). ASCII-Quittungen sind byte-identisch, weshalb ein ASCII-Korpus es nicht sieht — und
  weshalb unser eigener Durchgang vom 26.08. "no finding" meldete und am 28.08. oeffentlich
  korrigiert wurde. Die Korrektur steht im Abschnitt, nicht nur der Fund.

  EIGENER ABSCHNITT "Not measured": ob das Escaping als RFC-konform GEMEINT war (ihr Docstring nennt
  es, sagt aber nichts ueber die Absicht), Konformitaet jenseits der drei Vektoren, jeder Zustand
  ausserhalb der drei Staende, und wie die beiden Projekte sich einigen sollten — das ist ein
  Gespraech im Issue, keine Messung.

  KEINE WERTUNG UEBER DIE GEGENSEITE. Der Abschnitt sagt das ueber sich selbst und haelt es ein:
  jede Zeile an Commit und Datum gebunden, nichts ueber Absichten, nichts ueber unveroeffentlichte
  Arbeit. Eine Messung gegen einen gepinnten Stand sagt, was DIESER Baum tut, und nichts ueber einen
  Zweig, den niemand veroeffentlicht hat.

docs/PRIORITY_RECORD.md, Issue 147 als datierter oeffentlicher Nachweis
  Daten NICHT angenommen sondern gelesen (gh issue view): #147 eroeffnet 2026-08-19, offen, sieben
  Kommentare. Drei Eintraege tragen Messungen statt Vorschlaege, je an einen Commit gebunden.

  DER EINTRAG VOM 28.08. IST BEWUSST DRIN: es ist die Korrektur unserer EIGENEN frueheren Notiz.
  Ein Prioritaetsnachweis, der die Funde auffuehrt und die Ruecknahme weglaesst, waehlt seine
  eigenen Belege aus.

  UND DIE GRENZE, die das Dokument selbst zieht: es sagt ueber sich "each row is a public, dated
  artifact anyone can resolve". Das Umschlagprofil und die Abbildung sind am 30.08. GESCHRIEBEN
  worden, liegen auf einem ungemergten Zweig und sind damit KEIN oeffentlicher Nachweis. Sie stehen
  ausdruecklich als "not yet a public record" darin, und keine Zeile oben stuetzt sich auf sie. Sie
  werden ein datierter Nachweis, wenn sie veroeffentlicht sind, nicht wenn sie geschrieben sind.

Doku-Tests 39 passed, Konformitaet 79/79 unveraendert.
Kein Beitrag in Issue 147 und keiner in UKGovernmentBEIS/inspect_ai#4413 — nur gelesen.

Owner-GO: GO_OWNER_UMSCHLAGPROFIL_PHASE2_NO_RELEASE
…wei der drei Achsen gibt es in diesem Format gar nicht

Nachbesserung zu Phase 3 (Auftrag QITEM-UMSCHLAGPROFIL-UND-ROTE-GATES-01, Fassung 7), gefunden beim
Abgleich der eigenen Arbeit gegen die Definition of Done.

DER FUND
  Das Profil nennt als Gegenprobe zu R1 ausdruecklich DIE DREI Divergenzvektoren aus dem Faden:
  UTF-16-Schluesselordnung, 1e-7, ganzzahliger Float 2. Geliefert war eine einzige, und sie prueft
  das Escaping. R6 verlangt, dass wer das Profil beansprucht, die Gegenproben MITLIEFERT — fuer R1
  war das nicht erfuellt, und die Luecke stand zwischen Anspruch und Lieferung im selben Dokument.

GEMESSEN, statt sie einfach zu ergaenzen
  1e-7 und 2.0 KOENNEN in diesem Format nicht auftreten: _reject_non_jcs verweigert Python-Floats
  grundsaetzlich, Schwellwerte sind Dezimal-STRINGS. Ein Byte-Vergleich fuer diese beiden Achsen
  waere Theater — er verglaeche zwei Darstellungen eines Werts, den das Format nicht annimmt.

  Die ehrliche Gegenprobe fuer die zwei Achsen ist die VERWEIGERUNG selbst, und die liegt jetzt als
  Vektor vor: beide Objekte muessen verweigert werden. Neue Erwartungsachse canonicalizeRefuses im
  Laeufer, die die durchgelassenen Objekte im Fehlerfall ausdruckt.

  Die Formulierung im Profil ist entsprechend schwaecher und richtig: die zwei Achsen sind ENTFERNT,
  nicht GELOEST. Ein Format, das die Form nicht annimmt, in der ein Fehler auftritt, hat den Fehler
  nicht behoben.

DAZU: der erste R1-Vektor deckte weniger, als er konnte
  Neues Objekt {U+1F600:2, U+FF3A:1, "café":3, ...} — ein einziges Objekt, an dem die
  nicht-konforme Serialisierung BEIDE hier moeglichen Achsen falsch macht: die Reihenfolge (das
  Emoji steht nach Code-Units VORN, nach Codepoints HINTEN) und das Escaping. Vorher stand dort ein
  reines Escaping-Objekt.

META-TEST, und er hat sich zum vierten Mal bezahlt gemacht
  M "Kanonisierer -> legacy": 2 Faelle rot, gefangen.
  M "Float-Verbot entfernt": 0 rot — ABER das war keine entkommene Mutation, sondern eine
  WIRKUNGSLOSE. Ohne den Float-Zweig faellt der Wert auf 'unsupported value type' und wird weiterhin
  verweigert; mutiert war die Meldung, nicht die Eigenschaft. Ersetzt durch 'isinstance(value,
  float): return' — akzeptiert Floats wirklich — und DANN gefangen, mit den serialisierten Werten in
  der Ausgabe. Eine Mutation, die nichts aendert, beweist nichts ueber den Test.

Konformitaet 80/80 (vorher 79). Erzeuger-Neulauf hat alle Buendel neu geschrieben (frische
Wegwerfschluessel, im Kopf des Erzeugers dokumentiert). CONFORMANCE.md und die R1/R6-Abschnitte des
Profils auf die neuen Zahlen nachgezogen: zwoelf Vektoren, acht gepflanzte Defekte, acht gefangen,
DREI davon zuvor entkommen. Der Satz dazu steht jetzt im Profil: eine entkommene Mutation ist hier
mehr wert als eine gefangene — die gefangenen bestaetigen, was man ohnehin glaubte, die entkommenen
benennen, was man nicht wusste.

Owner-GO: GO_OWNER_UMSCHLAGPROFIL_PHASE2_NO_RELEASE
…verschiedenen Formen

Aus der faelligen un-Gegenlesung des Increments (80 substanzielle Dateien seit dem juengsten
Review-Beleg). Das Verdikt der Gegenlesung war in sich WIDERSPRUECHLICH — es beginnt mit REJECT,
arbeitet die Faelle durch und endet mit ACCEPT — und ihr einziger substanzieller Zweifel (D: ruft
build_eval_claim den Validator ueberhaupt?) ist am Quelltext geprueft und UNBEGRUENDET: Zeile 288,
vor der Zuweisung in 291. Ich habe die Praemisse nicht uebernommen, sondern gemessen.

DER ECHTE FUND kam als NEBENFRAGE der Gegenlesung: "was, wenn _coverage_error einen leeren String
zurueckgibt?" Am Quelltext nachgesehen pruefen die drei Aufrufstellen NICHT in derselben Form:

  Zeile 289 (build)   if err:            Wahrheitswert
  Zeile 321 (emit)    if err:            Wahrheitswert
  Zeile 398 (verify)  ... is not None    Identitaet

Ein leerer Fehlerstring ist falsy. Er passierte damit BEIDE Emit-Seiten und wuerde erst an der
Verify-Seite abgewiesen — das ist die emit-vs-verify-Asymmetrie, gegen die dieses Modul gebaut ist,
eine Ebene tiefer und in der Pruefform statt in der Pruefung.

EHRLICH: das ist LATENT, nicht live. Gemessen ueber den AST: 7 return-Anweisungen in
_coverage_error, davon 0 mit leerem String. Der Defekt tritt heute nicht auf. Aber zwei Formen, die
sich widersprechen, sind genau der Weg, auf dem eine spaetere Aenderung ihn still oeffnet — und der
Zwilling _typed_digest_error in decision.py stand bereits einheitlich auf `is not None`.

BEHOBEN: alle drei Stellen auf `is not None`. Dazu zwei Tests, die die Eigenschaft festnageln statt
sie zu beschreiben — kein Rueckgabewert ist ein leerer String (ueber den AST geprueft, nicht per
grep), und keine Aufrufstelle prueft den Wahrheitswert. Die Begruendung steht im Docstring der
Funktion, samt der Herkunft aus der Gegenlesung.

ANTI-TAUTOLOGIE: zwei gepflanzte Defekte, beide gefangen — Ruecknahme auf `if err:` (1 failed) und
Einfuehrung eines leeren Fehlerstrings (1 failed). Datei danach byte-identisch.

41 Tests der drei neuen Dateien gruen, ruff clean.

Owner-GO: GO_OWNER_UMSCHLAGPROFIL_PHASE2_NO_RELEASE
…egen den ausgelieferten Korpus gefunden

READ-PASS auf die eigene Ausgabe. Beide Funde betreffen NUR die Beschreibung, nicht den Code — und
beide sind Ungenauigkeiten in unsere eigene GUNST, was sie schlimmer macht, nicht harmloser.

1) R1 nannte ein Zeichenpaar, das der Vektor nicht verwendet
   Text: "U+1F600 und U+FFFF". Ausgeliefert: U+1F600 mit U+FF3A. Gemessen wurde BEIDES: beide Paare
   zeigen die Divergenz, weil ein Surrogatpaar nach Code-Units VOR jedem BMP-Zeichen sortiert und
   nach Codepoints hinter den meisten. Der Text nennt jetzt das ausgelieferte Paar und haelt fest,
   dass das Paar aus dem Faden identisch divergiert — statt ein Paar zu nennen, das nirgends laeuft.

2) R6 zaehlte eine Luecke als "entkommenen gepflanzten Defekt", die keiner war
   Die fehlenden R1-Achsen kamen aus dem ABGLEICH des Dokuments gegen den Korpus, nicht aus einer
   Mutation. Das ist kein Wortklauben: ein Meta-Test misst, ob eine AUSGELIEFERTE Pruefung
   fehlschlagen kann — er kann eine Pruefung, die nie ausgeliefert wurde, gar nicht bemerken. Wer
   beides zusammenzaehlt, schreibt dem Meta-Test eine Faehigkeit zu, die er nicht hat, und verlaesst
   sich beim naechsten Mal darauf.

   Die Zaehlung steht jetzt ausgeschrieben statt zusammengefasst, weil der Weg mehr sagt als die
   Zahl: acht gepflanzt, acht gefangen · ZWEI beim ersten Versuch entkommen (je ein fehlender Vektor
   gekauft) · ZWEI Mutationsversuche WIRKUNGSLOS statt entkommen (sieht in der Ausgabe identisch aus
   und ist etwas voellig anderes) · EINE Luecke ganz ohne Meta-Test gefunden.

CONFORMANCE.md entsprechend nachgezogen. doc-link-check PASS, 152 lokale Links, 0 kaputt.

Owner-GO: GO_OWNER_UMSCHLAGPROFIL_PHASE2_NO_RELEASE
…nd ein Absturz zaehlte wie eine Verweigerung

Zwei Funde aus der zweiten un-Gegenlesung (Gegenstand: der neue envelope_profile_rule-Handler, den
die erste Gegenlesung nicht abdeckte). Beide VOR dem Fix am Quelltext bestaetigt, nicht aus dem
Verdikt uebernommen.

FUND A — ueber-deklarierte Faelle wurden still auf der ersten Achse gruen
  Der Handler ist eine if-Kette und kehrt bei der ERSTEN passenden Erwartungsachse zurueck.
  Unter-Deklaration war von Anfang an fail-closed ("ein Fall, der keine deklariert, ist ein FAIL").
  UEBER-Deklaration war es nicht — und das ist dasselbe Loch von der anderen Seite.

  GEMESSEN: ein Fall mit korrektem contentRootHex UND einer classification "voelliger_unsinn"
  lieferte ok=True. Die zweite Achse wurde nie geprueft.

  Ein Fall, der nicht fehlschlagen kann, ist der leise Weg, auf dem ein Korpus waechst und nichts
  mehr misst. Jetzt: genau EINE Achse, geprueft BEVOR eine davon laeuft, mit der Aufzaehlung im
  Fehlertext.

FUND B — RecursionError entkam canonicalize
  GEMESSEN an drei Eingaben: int-Schluessel → EvalClaimError (gefangen), set als Wert →
  EvalClaimError (gefangen), 3000-fach geschachteltes Objekt → RecursionError, NICHT gefangen.

  Bei der Achse canonicalizeRefuses waere ein solcher Fall aus dem Handler geflogen und vom Laeufer
  als FAIL gemeldet worden — obwohl die Verweigerung faktisch stattgefunden hat. Ein falsches
  Negativ.

  ABER NICHT EINFACH MITFANGEN: ein Absturz ist etwas anderes als eine typisierte Ablehnung, und
  beide gleichzusetzen versteckt einen Robustheitsdefekt hinter einem gruenen Fall. Der Absturz
  zaehlt jetzt als Verweigerung (er taeuscht kein Bestehen vor) UND wird im detail getrennt
  ausgewiesen: "N of them by an UNTYPED crash (RecursionError), not a typed rejection".

Drei Tests dazu, darunter die Positivkontrolle — ohne sie bestuende ein Handler, der ALLES ablehnt,
diese Klasse. Anti-Tautologie: den Riegel auf `== 0` zurueckgenommen → 1 failed. Datei wiederher-
gestellt, 20 Tests gruen, conformance 80/80, ruff clean.

Zur Gegenlesung selbst, weil das zur Ehrlichkeit gehoert: ihr Verdikt war REJECT und beide Funde
waren echt. Die erste Gegenlesung desselben Increments war in sich widerspruechlich und ihr einziger
Zweifel unbegruendet. Zwei Laeufe, zwei sehr verschiedene Ertraege — der Unterschied lag am
mitgeschickten Kontext, nicht am Modell.

Owner-GO: GO_OWNER_UMSCHLAGPROFIL_PHASE2_NO_RELEASE
…telung — der Fund war falsch, die Luecke echt

Dritte un-Gegenlesung, Gegenstand classify_eval_claim (die neueste und sicherheitsrelevanteste
Ergaenzung: sie entscheidet gueltig / abgelehnt / ungueltig).

DER GEMELDETE FUND IST WIDERLEGT. Die Gegenlesung meldete REJECT mit der Begruendung, ein
RecursionError entkomme dem except-Block und breche die dokumentierte never-raise-Zusage.
GEMESSEN, statt uebernommen:

  load_claim_text("[" * 20000 + "]" * 20000)   → EvalClaimError "JSON nesting is too deep"
  classify_eval_claim(<Bundle mit dieser Nutzlast>)  → invalid, kein Wurf
  dasselbe mit 5000-fach geschachteltem OBJEKT      → invalid, kein Wurf
  decode_eval_claim direkt, beide Formen            → None, kein Wurf

Grund: load_claim_text delegiert an den budget-begrenzten strikten Parser, der Tiefe (CWE-674) auf
einen typisierten Fehler abbildet — das steht so auch im Docstring der Funktion. Der RecursionError
tritt nicht auf.

DIE LUECKE, AUF DIE ER ZEIGTE, WAR ECHT. Die never-raise-Eigenschaft war bisher nur an FLACHEM
Unsinn geprueft (Nicht-Bundles, tote Pfade, None, Zahlen). Genau die interessante Eingabe fehlte.
Vier Tests ergaenzt: tiefe Liste, tiefes Objekt, dieselbe Zusage eine Ebene tiefer in
decode_eval_claim, und explizit, dass die Abweisung TYPISIERT ist und kein roher RecursionError.

Damit ist das die dritte Gegenlesung dieses Increments, deren Praemisse den Kontakt mit dem
Quelltext nicht ueberlebt hat — und das dritte Mal, dass die Pruefung billig war und die Alternative
(uebernehmen) einen falschen Fix erzeugt haette. Der Ertrag der Gegenlesungen liegt bisher nicht in
ihren Verdikten, sondern in den Stellen, auf die sie zeigen.

12 Tests der Datei gruen, ruff clean.

Owner-GO: GO_OWNER_UMSCHLAGPROFIL_PHASE2_NO_RELEASE
…und die Klasse dahinter ist jetzt gebunden

Gefunden bei einer systematischen Selbstpruefung: JEDE pruefbare Zahl der beiden neuen Dokumente
gegen einen Befehl DERSELBEN Runde gehalten, statt sie stehen zu lassen.

FUND 1 — "twelve vectors", gezaehlt sind DREIZEHN
  R1 drei, R2 drei, R3 zwei, R4 zwei, R5 drei. Die Zahl war nach dem Hinzufuegen des
  Float-Verweigerungs-Vektors nicht mitgewachsen. Korrigiert in Profil und CONFORMANCE.md, samt der
  Aufschluesselung je Regel, damit sie beim naechsten Mal beim Lesen auffaellt.

FUND 2 — "eight planted defects, eight caught" war ungenau
  Nachgezaehlt: NEUN wirksame gepflanzte Defekte, neun gefangen (sieben im vollen Korpus-Durchgang,
  zwei fuer die spaeter ergaenzten R1-Vektoren). Die zwei WIRKUNGSLOSEN Versuche sind ausdruecklich
  NICHT unter den neun — eine Mutation, die nichts aendert, ist kein Defekt, den der Korpus zu
  fangen versaeumt hat. Sie zaehlten vorher stillschweigend mit und machten die Zahl kleiner UND
  falscher zugleich.

DER KLASSENFIX, nicht nur der Instanzfix
  Der Instanzfehler ist eine veraltete Zahl. Der KLASSENfehler ist, dass eine Zahl in einem Dokument
  an nichts gebunden war und nur durch Hinsehen stimmen konnte. Neuer Test
  test_die_zahl_im_text_ist_die_gemessene_zahl: er zaehlt die case.json unter envelope_profile/,
  bildet die Zahl auf ihr Zahlwort ab und verlangt, dass JEDE Zeile in Profil und CONFORMANCE.md,
  die "envelope_profile/" und " vectors" nennt, dieses Wort traegt. Waechst der Korpus, faellt der
  Test, bis der Text nachgezogen ist.

  Anti-Tautologie: Text auf "twelve" zurueckgesetzt → 1 failed. Datei wiederhergestellt, 13 Tests
  gruen.

Nebenbefund derselben Pruefung, ohne Handlungsbedarf: alle zitierten Fundstellen stimmen zeilengenau
(canonical.py:43 CONTENT_ROOT_ALG, bundle.py:770 merkle.leaf_hash(payload), merkle.py:34 def
leaf_hash, intoto.py:426 "sampleSize": claim["n"], decision-receipt Zeile 13 sha256Digest / Zeile 20
relationDigest). Und die Aussage "null von neun Schemata" ist im Text durchgehend in der
Vergangenheitsform gehalten — heute traegt EINES die Felder, weil wir sie ergaenzt haben.

doc-link-check PASS (152 Links, 0 kaputt), claims-hygiene PASS (49 Dokumente, 0 Verstoesse).

Owner-GO: GO_OWNER_UMSCHLAGPROFIL_PHASE2_NO_RELEASE
…and — jetzt am Entwurf selbst geprueft, und eine Aussage hat das nicht ueberlebt

Die Abbildung zitierte vier Abschnitte von draft-mih-sokolov-scitt-payload-binding-02 (4.1, 7.1, 8,
1.1) und stellte sie dar, als haette ich sie geprueft. Gemessen war das nicht: der Entwurf liegt
NICHT lokal, und die Nummern stammten vollstaendig aus einer Lueckenanalyse vom selben Tag. Eine
uebernommene Praemisse, die wie eine eigene Messung aussah.

GEPRUEFT, am Entwurfstext selbst:
  Datatracker : Revision 02, 24.08.2026, individuelle Einreichung, kein Stream, kein intendierter
                RFC-Status — deckt sich mit dem, was im Profil steht
  4.1  BESTAETIGT  Registereintrag jcs: "Plain RFC 8785 JCS, no normalization pass; SHA-256;
                   lowercase hex output"
  7.1  BESTAETIGT  leaf_input = bytes.fromhex(D), rohe 32 Byte
  8    BESTAETIGT  vier Felder, type/digest_alg/digest PFLICHT, purpose BEDINGT
  1.1  EXISTIERT   aber sagt etwas anderes als von mir behauptet

DIE AUSSAGE, DIE NICHT UEBERLEBT HAT
  Ich schrieb, R5 und R6 gebe es, "weil der Entwurf in seinem Abschnitt 1.1 selbst sagt, dass er sie
  nicht behandelt". Gelesen sagt 1.1 das NICHT: er schliesst Payload-Formate, Artefakttypen,
  Anwendungsbedeutung, Registrierungspolitik und Transporte aus — und nennt Abdeckung und
  Schluesselvertrauen ueberhaupt nicht.

  ABWESENHEIT IST ETWAS ANDERES ALS EIN AUSDRUECKLICHER AUSSCHLUSS. Die erste ist die schwaechere
  Aussage und die vom Text getragene. Die zweite haette dem Entwurf eine bewusste Entscheidung
  zugeschrieben, die er nicht getroffen hat — und haette unsere Ergaenzung besser aussehen lassen,
  als sie belegbar ist. In beiden Dokumenten korrigiert.

DAZU EINE SCHAERFUNG BEI G2, die erst das Lesen ermoeglicht hat
  7.1 nennt ausdruecklich auch die FALSCHE Alternative: D.encode("utf-8"), also 64 ASCII-Byte.
  Unsere Konstruktion ist WEDER die richtige NOCH die genannte falsche — wir binden den Payload
  selbst, nicht die Kennung in irgendeiner Form. Wir machen also nicht den Fehler, vor dem der
  Entwurf warnt, sondern etwas Drittes. Das steht jetzt so da; vorher las es sich, als waeren es nur
  zwei Moeglichkeiten.

Der Kopf der Abbildung nennt jetzt die Quelle der Entwurfsseite samt der Tatsache, dass eine
Behauptung die Pruefung nicht bestanden hat. doc-link-check PASS (152 Links), 13 Tests gruen.

Owner-GO: GO_OWNER_UMSCHLAGPROFIL_PHASE2_NO_RELEASE
…ymmetrie zum Entwurf steht jetzt dabei

Zweite uebernommene Praemisse desselben Zuges nachgeholt. Beide Dokumente nannten RFC 9943 als "die
veroeffentlichte SCITT-Architektur", ohne dass ich das geprueft haette.

GEPRUEFT beim RFC Editor am 30.08.2026: RFC 9943, "An Architecture for Trustworthy and Transparent
Digital Supply Chains", Juni 2026, STANDARDS TRACK, IETF-Stream, Arbeitsgruppe SCITT. Titel und
Charakterisierung decken sich woertlich. Die Aussage traegt — anders als die Aussage ueber Abschnitt
1.1 des Entwurfs, die im Commit davor nicht getragen hat.

Die geprueften Angaben stehen jetzt im Text, und mit ihnen die ASYMMETRIE, die vorher nur implizit
war: auf der einen Seite ein Standards-Track-RFC einer Arbeitsgruppe, auf der anderen eine
individuelle Einreichung ohne Stream und ohne intendierten RFC-Status. Das ist kein Werturteil ueber
den Entwurf, sondern eine Tatsache ueber seinen Stand — und sie aendert, was eine Abweichung von dem
einen gegenueber dem anderen bedeutet. Ohne sie liest sich "wir weichen ab" bei beiden gleich schwer.

doc-link-check PASS, 152 lokale Links, 0 kaputt.

Owner-GO: GO_OWNER_UMSCHLAGPROFIL_PHASE2_NO_RELEASE
…n Tage aelterer Entwurf weist genau diese Form zurueck

Owner-Berichtigung Fassung 8 vom 30.08.2026, Digest gegen die Datei gerechnet: 28017 Byte,
ed5b408f8208cc627f3ed1a71f60a3895939673d8a7816ca819d92be42b8b3f7 — stimmt. Der Farmer trug noch
Fassung 7 (25817 Byte, 846d5e86…); die Bruecke holte sie zunaechst NICHT, weil die lokale Datei mit
ihrem eigenen veralteten Manifest stimmig war. Erst bridge-from-mac brachte sie.

DER ANLASS, gemessen und nicht vermutet
  draft-hillier-coverage-attestation-00, The Coverage Attestation Profile (CAP-1), Joel David
  Hillier, Certisyn Inc., 20.08.2026, im Archiv von scitt@ietf.org. Seine normative Zeile: je
  Stratum muessen die in Frage kommenden Einheiten gleich den geprueften plus den EINZELN
  BEGRUENDETEN ungeprueften sein, und ein Rest, der sich nur durch Subtraktion ausgleicht, MUSS
  abgelehnt werden.

  Unsere drei Zahlen sind genau so ein Rest — die dritte folgt aus den beiden anderen. Ein
  oeffentlicher Entwurf, ZEHN TAGE AELTER als unser Vorschlag, weist die Form ausdruecklich zurueck.

ENTFERNT, nicht stehen gelassen
  schemas/eval_claim_v0_1.schema.json  coverage-Block raus; Eigenschaften wieder 21 und namens-
                                       identisch mit origin/main (gemessen, nicht angenommen)
  src/proofbundle/evalclaim.py         _coverage_error, der _OPTIONAL-Eintrag, der build-kwarg und
                                       alle DREI Aufrufstellen (build/emit/verify)
  tests/test_eval_claim_coverage.py    ganze Datei
  conformance/envelope_profile/        die drei R5-Vektoren
  conformance/run_conformance.py       die Achse coverageDistinguishable samt Handler-Zweig
  conformance/vector_schema.json       Achsenliste nachgezogen
  conformance/manifest.json            80 -> 77 Faelle

NICHT ANGEFASST, und das ist eine Abgrenzung, keine Nachlaessigkeit
  Das RT-10-Tripel in scripts/findings_register.py und scripts/audit_candidate_matrix.py fuehrt
  population_size und evaluated_count in derselben Form. Es ist VORBESTEHEND, intern und nicht
  Gegenstand dieser Berichtigung. In der Abbildung ausdruecklich vermerkt, weil ein Leser es sonst
  findet und sich wundert.

NICHTS NACHGEBAUT
  Keine CAP-1-foermigen Felder erfunden. Gelesen sind die Zusammenfassung und zwei von 129
  Nachrichten des Fadens; der Entwurf selbst ist NICHT GELESEN. Ein aus einer Zusammenfassung
  nachgebautes Feldwerk waere geraten.

R5 NEU GESCHRIEBEN
  Sie benennt weiter die ANFORDERUNG (ein Konsument muss "wir haben nachgesehen und nichts gefunden"
  von "wir haben nie nachgesehen" unterscheiden koennen), nennt KEINE eigene Feldform mehr, tritt
  NICHT mehr als unser Beitrag auf, verweist auf CAP-1 als die Arbeit, die die Frage spezifiziert,
  und haelt fest, dass die Feldform von proofbundle NICHT ENTSCHIEDEN ist. Dazu die ehrliche Grenze
  ueber den eigenen Lesestand.

R5 OHNE GEGENPROBE, als Entscheidung gefuehrt
  Eine Gegenprobe gegen eine gerade zurueckgezogene Form pruefte nichts. Der Deckungstest verlangt
  jetzt R1 bis R4 — und haelt mit assertNotIn ausdruecklich fest, dass R5 KEINE Vektoren haben soll:
  eine stillschweigend fehlende Regel sieht aus wie eine vergessene, und taucht spaeter doch ein
  R5-Vektor auf, ist die Entscheidung unbemerkt zurueckgenommen.

DER ZAHLEN-RIEGEL HAT SICH SOFORT BEZAHLT GEMACHT. Der gestern gebaute Test
test_die_zahl_im_text_ist_die_gemessene_zahl schlug beim ersten Lauf nach der Ruecknahme fehl:
13 Vektoren im Text, 10 gezaehlt. Genau dafuer ist er da.

Konformitaet 77/77, 78 Tests der beruehrten Dateien gruen, ruff clean (die eine E902-Meldung war
die noch nicht gestagte Loeschung, nicht ein Befund), mypy 65 Dateien ohne Befund, doc-link-check
152 Links, claims-hygiene 49 Dokumente.

DER ZWEIG WAR NIE GEPUSHT: git ls-remote --heads origin nennt ihn 0 Mal, es gibt keine
remote-tracking-Ref und FETCH_HEAD kennt ihn nicht. Die zurueckgezogenen Felder waren zu keinem
Zeitpunkt oeffentlich sichtbar.

Owner-GO: GO_OWNER_UMSCHLAGPROFIL_PHASE2_NO_RELEASE
…ie statische Vorpruefung, die das in Sekunden statt in Stunden zeigt

Phase 1 des Auftrags. Die zwei Luecken des Tores sind BEIDE GAP_STALE: kein einziger Mutant hat
ueberlebt. Der Auftrag fragt je Luecke "welcher Test haette sie fangen muessen" — die ehrliche
Antwort ist zweimal KEINER, weil die Muster, die die Operatoren ersetzen wollen, im Quelltext nicht
mehr existieren.

  #58  src/proofbundle/renewal.py:841
       erwartet  "mldsa" in (newest.sig_alg or "")
       steht     "mldsa" in _sig_label
       seit      38a672a
  #80  src/proofbundle/relation_statement.py:323
       erwartet  rel0 in _SELF_ASSERTED_RETRACTORS
       steht     is_member(rel0, _SELF_ASSERTED_RETRACTORS)
       seit      fd84e1d

DIE GEMEINSAME WURZEL, und sie ist der eigentliche Ertrag: BEIDE ausloesenden Commits waren
KLASSENFIXES. 38a672a normalisierte sig_alg, weil ein nicht-String aus einem angreifergebauten ATS
den Mitgliedstest mit rohem TypeError abstuerzen liess. fd84e1d heisst woertlich "27 Mitgliedstests
hashten Angreiferdaten — die Klasse, nicht die 27 Zeilen".

Beide Verbesserungen waren richtig. EIN KLASSENFIX, DER N AUFRUFSTELLEN VERBESSERT, ENTWERTET STILL
JEDEN PRUEFER, DER DIESE STELLEN WOERTLICH ZITIERT. Das ist die Klasse hinter beiden Luecken, und
sie ist teuer: das Tor bemerkt es erst nach einem Lauf von rund dreieinhalb Stunden, weil jeder
Operator eine volle Testsuite kostet.

BEIDE OPERATOREN GEZOGEN, und zwar BEIDE ZEILEN je Eintrag. Zoege man nur das Muster, brachte der
Ersatz die alte Form zurueck — die Mutation aenderte dann die Wache UND die Haertung, also zwei
Dinge statt des einen, das ihre Bezeichnung nennt. Ein Operator, der mehr mutiert als er sagt, misst
wieder etwas anderes als er behauptet. Die semantische Mutation ist unveraendert; das ist KEINE
Schwaechung, sondern die Wiederherstellung dessen, was der Laeufer in seinem eigenen Kommentar
verlangt: "An operator whose label says 'disabled' must actually disable".

DER KLASSENFIX: scripts/mutation_operators_fresh.py
  Die Frage "findet jedes Muster noch seine Datei" ist REIN STATISCH und in Sekunden zu beantworten.
  Die Vorpruefung beantwortet sie, bevor die teure Arbeit beginnt. Sie ersetzt das Tor NICHT — sie
  sagt nichts darueber, ob ein Mutant getoetet wird — sie sagt nur, ob die Operatoren ueberhaupt noch
  auf den Code zeigen, den zu pruefen sie behaupten.

  Sie hat sich beim Bauen selbst bewiesen: nach dem Fix von #58 meldete sie sofort "1 von 88",
  naemlich #80, und nach dessen Fix "alle 88 finden ihr Muster".

WIE GEMESSEN: paralleler Sweep ueber alle 88 Operatoren, der _prepare_workdir und _red_count
UNVERAENDERT aus mutation_check.py importiert statt sie nachzubauen. Ergebnis "88 Operatoren, 2
Luecke(n), 2573s", Baseline 1. Ein erster Anlauf wurde VERWORFEN, bevor ich ihm geglaubt habe: er
zaehlte mit eigener Regex und ohne die Stale-Bytecode-Abwehr des Laeufers und haette eine ANDERE
GROESSE gemessen — ein daraus benannter Luckenname haette diese Phase auf eine falsche Faehrte
geschickt.

OFFEN und im naechsten Schritt: der Nachweis, dass beide Mutanten jetzt auch GETOETET werden (je ein
Lauf mit und ohne Mutation), und danach die volle Gegenprobe.

Owner-GO: GO_OWNER_UMSCHLAGPROFIL_PHASE2_NO_RELEASE
…hrdeutige Muster mutieren still die falsche Stelle

Aus der vierten un-Gegenlesung, Gegenstand die Phase-1-Reparatur. Ihr Verdikt war REJECT mit vier
Punkten. ZWEI davon habe ich am Quelltext geprueft und sie treffen NICHT zu, EINER ist echt und
umgesetzt, EINER laeuft noch als Messung.

GEPRUEFT UND WIDERLEGT
  A) "das Muster koennte weniger als die ganze Zeile decken, dann bliebe ein Rest stehen" — gemessen:
     beide Muster decken die GANZE Zeile, kein Rest haengt dran.
  D-Teil) "str.replace koennte die falsche Stelle treffen" — gemessen: beide Muster kommen genau
     1x vor, und ueber ALLE 88 Operatoren gibt es 0 mehrdeutige Muster.

ECHT UND UMGESETZT
  Auch wenn der Fall heute nicht auftritt, ist die FRAGE berechtigt und war nicht gestellt.
  `str.replace(alt, neu, 1)` trifft die ERSTE Fundstelle. Kommt ein Muster mehrfach vor, mutiert der
  Operator still den falschen Ort — er laeuft durch, meldet nichts, und misst etwas anderes als
  seine Bezeichnung sagt. Das ist LEISER als ein fehlendes Muster und damit gefaehrlicher.

  "Kommt genau einmal vor" ist genauso statisch pruefbar wie "kommt vor". Die Vorpruefung stellt
  jetzt BEIDE Fragen. Anti-Tautologie: ein kuenstlich mehrdeutiges Muster ("def ") wird mit
  "Muster kommt 14x vor (erwartet genau 1x)" gefangen.

DIE EHRLICHE GRENZE STEHT JETZT IM DOCSTRING, und sie kam auch aus der Gegenlesung: diese
Vorpruefung faengt SYNTAKTISCHE Veralterung. Sie faengt NICHT den Fall, dass das Muster noch passt,
waehrend die Logik DRUMHERUM sich geaendert hat — etwa eine neue Bedingung neben der zitierten, die
die mutierte Wache kompensiert. Dann meldet sie OK, und der Operator misst trotzdem weniger, als er
behauptet. Dagegen hilft nur der volle Lauf. Ohne diesen Satz laedt ein gruenes
[mutation-operators-fresh] dazu ein, den teuren Lauf fuer erledigt zu halten.

NOCH IN MESSUNG (Punkt B der Gegenlesung): "3 rot statt 1" ist das Kriterium des Tores und bleibt es,
sagt aber nicht, OB die Erhoehung von der abgeschalteten Wache kommt. Ein Lauf misst gerade, WELCHE
Tests durch die Mutation rot werden. Ergebnis folgt im Bericht.

Owner-GO: GO_OWNER_UMSCHLAGPROFIL_PHASE2_NO_RELEASE
… richtig ist — im Quelltext, nicht nur im Journal

B5 des Auftrags 20260830T151549Z (Digest geprueft: 5863 Byte, db90a793363558174005cb92bda4650d…).
Die Owner-Entscheidung aus Karte OA-4a8daddb55 gehoert dorthin, wo ein Leser des Repositories das
rote Gate erklaert findet — ein rotes Gate ohne Grund in derselben Stelle liest sich fuer jeden
spaeteren Leser wie unerledigte Arbeit.

DIE BEGRUENDUNG, im Docstring von c12_1_pretag_audit:
  Ein Receipt bindet einen subject_tree_digest. Ein Arbeitszweig ist nicht fertig und bekommt beim
  Merge mindestens einen weiteren Commit — ein Receipt gegen ihn bezeugt also einen Baum, der gleich
  aufhoert zu existieren. Es zu erzeugen waere GENAU DER VORGANG, gegen den diese Pruefung gebaut
  wurde: sie wuerde den Befund REPRODUZIEREN statt ihn zu schliessen. C12.1 ist ein RELEASE-Gate;
  das Receipt gehoert zu dem Baum, der tatsaechlich getaggt wird.

  Ein rotes C12.1 auf einem Zweig ist damit weder unerledigte Arbeit noch ein Werkzeugfehler,
  sondern die Pruefung, die ihre Arbeit an einem Gegenstand tut, den sie nicht segnen soll.

MITGESCHRIEBEN, weil es am selben Tag gemessen wurde: das v5.0.0-Receipt bindet 4212087273dc… und
das IST der subject_tree_digest des Tags v5.0.0; es ist vom gepinnten Schluessel signiert, und
dieses Gate meldet ok, wenn man es gegen JENEN Baum faehrt. C12.1 ist fuer das veroeffentlichte
5.0.0 also nicht offen — es erscheint nur offen, wenn das Gate gegen einen anderen Baum laeuft.

UND DIE EHRLICHE GRENZE STEHT DANEBEN, statt geglaettet zu werden: der signierte
audit_output_digest des Receipts loest sich auf kein auffindbares Artefakt auf, und nichts in diesem
Gate loest ihn auf. Das Feld ist SIGNIERT — das macht es faelschungsevident und zurechenbar, nicht
pruefbar. Vorgelegt als Befund PRETAG-RECEIPT-AUDIT-OUTPUT-DIGEST-IST-SIGNIERT-ABER-UNAUFLOESBAR-01.

Verhalten unveraendert: der Code der Pruefung ist nicht angefasst, nur ihr Docstring. C12.1 bleibt
auf diesem Zweig rot, wie vom Owner entschieden. ruff clean.

Owner-GO: GO_OWNER_UMSCHLAGPROFIL_PHASE2_NO_RELEASE
…ung, Begruendung gemessen

Eine adversariale Gegenlesung verwarf beide Verdikte als zu guenstig. Zwei ihrer drei
Begruendungen trugen am Quelltext nicht, eine traf.

TRAEGT NICHT: sie las intoto.py:178 als VERWENDUNG eines sort_keys-Koerpers unter dem
jcs-Token. Die Funktion _content_root_binding weist genau das ZURUECK, ihr Docstring sagt
"is rejected". Bei der Pruefung fiel zusaetzlich auf, dass intoto.py:126 ensure_ascii=False
faehrt, also rohes UTF-8 — die richtige Richtung, und in keinem Dokument vermerkt.

TRAEGT NICHT: sie nannte die G2-Unterscheidung Spitzfindigkeit. Abschnitt 7.1 regelt, wie
eine ABGELEITETE KENNUNG in den Baum geht; wir uebergeben gar keine Kennung. Die Vorbedingung
der Regel haelt hier nicht — das aendert die Diagnose, nicht die Folge.

TRAEGT: "ein Umbau wuerde jeden bereits ausgestellten Beleg entwerten" stand als Begruendung
da, ohne dass die Zahl dahinter je erhoben wurde. Gemessen: 91 Buendel mit merkle und
payload_b64 im Baum (von 3000 geprueften JSON-Dateien), 44 veroeffentlichte Versionen
0.3.0 bis 5.0.0. Was Dritte mit der Bibliothek ausgestellt haben, ist von hier NICHT MESSBAR
— die Zahl ist eine Untergrenze, keine Groesse.

Beide Verdikte umgestellt: die betriebliche Tatsache fuehrt, der Geltungsbereich steht vor
der Behauptung. G2 sagt jetzt zuerst "nicht interoperabel". G1 sagt zuerst, dass die
Identitaets-Aussage nur den deklarierten jcs-sha256-v1-Pfad betrifft und keine Aussage ueber
die ganze Bibliothek ist. Der Widerspruch zum Abschnitt "nicht gemessen" ist aufgeloest.

Keine Aussenwirkung, nichts gepostet, nichts auf main.
… Konstruktion gar nicht

Der Beleg-Riegel monierte den Satz "die Vorbedingung der Regel haelt hier nicht" als
unbelegt. Zu Recht: ich hatte ihn aus dem eigenen Dokument uebernommen, nicht am Entwurf
gemessen. Der Entwurf lag nirgends lokal.

GEHOLT UND GELESEN: draft-mih-sokolov-scitt-payload-binding-02.txt, 92428 B,
sha256 47ab675797d7edfe, von ietf.org/archive/id.

WAS 7.1 WIRKLICH SAGT — erster Satz: "This profile imposes no leaf construction on a
Verifiable Data Structure." Das MUST danach ist BEDINGT: "Where a Transparency Service's
VDS keys its log on the derived identifier". Wir schluesseln nicht auf der abgeleiteten
Kennung, wir binden den Payload. Die Bedingung erreicht uns nicht — es gibt hier keinen
Mangel zu erklaeren.

DAMIT WAR MEINE EIGENE VORHERIGE KORREKTUR FALSCH, in der Gegenrichtung. Ich hatte zwei
der drei Gegenlesungs-Gruende am Quelltext verworfen und trotzdem ihren RAHMEN uebernommen
("G2 ist zu weich") und das Verdikt auf "not interoperable" verschaerft, ohne 7.1 neu zu
lesen. Einen Fehler im eigenen Artefakt zu behaupten, den es nicht gibt, ist derselbe
Verstoss wie einen zu verschweigen, und schwerer zu bemerken, weil es sich nach Strenge
anfuehlt.

WAS UNS STATTDESSEN BINDET, und das war nicht verbunden: Abschnitt 5.1 macht die
Repraesentation normativ — eine Payload-Klasse MUSS angeben, welche sie je Feld benutzt,
ein Verifier DARF NICHT still konvertieren. Diese Pflicht gilt uns unabhaengig von 7.1,
und diese Seite loest sie ein.

Berichtigt: G2-Verdikt, die Entwurfs-Zeile (zitierte die Regel ohne ihren ersten Satz und
ohne die Bedingung), die Zusammenfassungszeile ("hard divergence" -> ausserhalb von 7.1),
und der Protokoll-Abschnitt fuehrt jetzt BEIDE Irrtuemer der Runde.

Keine Aussenwirkung, nichts auf main.
…fiel durch

Fix-die-Klasse zum vorigen Commit. G2 war falsch, WEIL die Aussage ueber fremden Normtext aus
dem eigenen Dokument uebernommen und nicht am Original gemessen war. Jede andere
Entwurfs-Aussage derselben Seite hat dieselbe Herkunft — also im selben Durchgang gesweept,
statt nur die eine Instanz zu schliessen.

Geprueft gegen draft-mih-sokolov-scitt-payload-binding-02.txt, 92428 B,
sha256 47ab675797d7edfe, von ietf.org/archive/id. Sechs Aussagen:

4.1  jcs-Registereintrag                       HAELT — aber kursiv wie ein Zitat gesetzt,
                                               obwohl Paraphrase; jetzt als solche markiert
4.1/5 Ausschlussmenge liegt AUSSERHALB des      NEU — war nicht verbunden. Unser
      Algorithmus                              statement_content_root entfernt nichts, in
                                               canonical.py kommt der Begriff null Mal vor.
                                               Beide stimmen ueberein, wenn die Payload-Klasse
                                               eine LEERE Ausschlussmenge deklariert — wir
                                               deklarieren keine, also dieselbe Pflicht wie 5.1
5.1  Repraesentation ist normativ              HAELT — das ist die Pflicht, die uns wirklich
                                               erreicht
7.1  Blattkonstruktion                         HIELT NICHT wie behauptet (im Vorcommit bereits
                                               berichtigt)
8    type/purpose/digest_alg/digest            HAELT EXAKT, samt Pflichtgraden
1.1  Abdeckung kommt nicht vor                 HAELT, und STAERKER als behauptet: coverage,
                                               population, evaluated_count, unresolved, sample
                                               haben zusammen 0 Treffer im GANZEN Dokument,
                                               nicht nur in 1.1

Eine von sechs fiel durch. Neue Abschnitts-Tabelle "Which draft claims were checked at source"
haelt Ergebnis und Herkunft je Aussage fest, damit ein spaeterer Leser sieht, welche Zeile
gemessen ist und welche uebernommen war.

Keine Aussenwirkung, nichts auf main.
…RKUNFT statt an ihrem NAMEN

hermetic-cleanroom fiel auf PR #159 mit `1 failed, 2379 passed`: ein Test las
docs/RECEIPT_ENVELOPE_PROFILE.md, und `docs/` wird vom sdist geprunt. Die Ableitung sah in dem
Modul NULL Pfade, konnte es also nicht als Repo-Kontext erkennen.

GEMESSEN, WARUM — zwei Ursachen, beide noetig:
  (1) _ROOT_NAMEN ist eine GROSSGESCHRIEBENE Allowlist; die lokale Variable hiess `root`. Damit war
      das GANZE Modul unsichtbar, nicht nur der eine Pfad.
  (2) Der Pfad stand hinter einer Schleifenvariable, und _kette verwirft ein variables Segment.

WARUM NICHT DER LISTENEINTRAG: der Kommentar ueber _REPO_CONTEXT_TESTS sagt woertlich, sie sei der
Rueckfall fuer Tests, die das Repo NICHT ueber ein Pfad-Literal im eigenen Modul erreichen. Dieses
Modul tut es. (Auf origin/main gemessen: die Ableitung existiert dort, 2 Treffer je Name in
conftest.py, 15 Listeneintraege.)

WARUM NICHT DIE NAMENSLISTE OEFFNEN: die Grossschreibung ist der UNTERSCHEIDER zwischen einer
modulweiten Konstante (meint konventionell die Wurzel) und einer lokalen Variable (meint meist
etwas anderes). Gemessen kostet das Oeffnen 14 Falsch-Positiv-Pfade in zwei Modulen, deren lokale
Variable ein kopiertes Korpus- (`root = self._copy_corpus()`) bzw. ein Temp-Verzeichnis
(`repo = tmp_path / "r"`) ist — die wuerden ausserhalb eines Checkouts still uebersprungen.

DER FIX, semantisch statt namensbasiert:
- _ist_dateiquelle / _schritte erkennen eine aus __file__ abgeleitete Wurzel und ZAEHLEN die
  parent-Schritte. Gebunden wird ein Name nur, wenn die Schrittzahl der Entfernung DIESES Moduls
  zur Wurzel entspricht. `Path(__file__).parent` ist das tests-Verzeichnis und bindet nicht —
  29 Stellen im Baum schreiben genau das.
- Schleifen ueber einem Tupel/einer Liste aus lauter String-Konstanten binden ihre Variable auf
  genau diese Werte, BESCHRAENKT auf den Schleifenkoerper. Wird sie im Koerper neu zugewiesen,
  bindet sie nicht (fail-closed).

DREI EIGENE FEHLER AUF DEM WEG, alle von Messungen gefangen, keiner von der Durchsicht:
- Bindung zuerst modulweit nach NAMEN -> dasselbe Modul hat zwei Schleifen ueber `rel`, und die
  Werte der einen erschienen in der Kette der anderen (`conformance/CONFORMANCE.md`, existiert
  nirgends). Jetzt je Schleife nur ihr Koerper.
- Tiefe zuerst um eins verschoben -> ein Schritt wurde akzeptiert, zwei nicht, genau invers.
- Tiefe zuerst gegen die globale _REPO_ROOT statt gegen den Parameter `wurzel` gerechnet -> ein
  Testmodul liegt nicht darunter, relative_to wirft, die Bindung fiel STILL aus. Gefangen von den
  neuen Tests.

GEMESSEN NACH DEM FIX: Zielmodul 5 Pfade, jeder existiert im Checkout; modul_ist_repo_kontext im
Checkout False, im sdist-artigen Baum ohne docs/ True; NULL Module gelten im vollen Checkout als
Repo-Kontext. Volle Suite 2599 passed, 8 skipped, 740 subtests, exit 0 (2593 vorher plus die sechs
neuen Tests). ruff sauber.

SECHS TESTS halten die Eigenschaften einzeln: lokale kleingeschriebene Wurzel wird erkannt ·
Schleife ueber Konstanten wird aufgeloest · ein Schritt zu wenig ist NICHT die Wurzel · eine
lokale Variable ohne Dateiherkunft ist keine Wurzel · eine im Koerper neu zugewiesene
Schleifenvariable bindet nicht · zwei Schleifen mit demselben Namen lecken nicht.

Befund HERMETIK-ABLEITUNG-SIEHT-PFADLITERAL-HINTER-EINER-INDIREKTION-NICHT-01 bleibt registriert.
@b7n0de
b7n0de marked this pull request as ready for review August 30, 2026 23:20
@b7n0de
b7n0de merged commit 27a84db into main Aug 30, 2026
21 of 22 checks passed
@b7n0de
b7n0de deleted the feat/umschlagprofil-und-zwei-rote-gates-20260830 branch August 30, 2026 23:21
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