Receipt envelope profile v0.1, mutation operator freshness gate, and the C12.1 scope correction - #159
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
mutationgate could not previously see, records whyaudit-candidate-matrixis 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
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.mdstates that explicitly.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.
mutation_operators_fresh.py, a pre-check — a script, not a wired gate.The existing
mutationgate can report a clean run while an operator silentlymatches 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 thisPR changes no file under
.github/workflows/. Wiring it is separate work.The relationship to RFC 9943 and to
draft-mih-sokolov-scitt-payload-binding-02.docs/SCITT_CPB_MAPPING.mdmaps our envelope clause by clause against thatdraft: where the behaviour agrees, where it diverges, and where a claim in the
draft did not survive checking at source.
INTEROP.mdadds a measurement recordagainst 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 removedagain, because
draft-hillier-coverage-attestation-00(20 August 2026) normativelyrefuses 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-matrixis red on this branch on purpose, and the reasoning nowlives in the docstring of
c12_1_pretag_auditrather than only in a commit message.A receipt binds a
subject_tree_digest. A working branch is not a release, and itstree 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 twonon-PASS entries are declared states, not failures:
C6.3(the 24h soak artifactrecords 90s) and
EXT.1(the deliberately open external human audit).The hermetic-cleanroom repair
hermetic-cleanroomfirst failed on this branch (1 failed, 2379 passed): a test added here readsdocs/RECEIPT_ENVELOPE_PROFILE.md, which the sdist prunes, andconftest.py's derivation could notsee it — the module binds its root to a lowercase local (
root = Path(__file__)...), and the pathsits 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()andrepo = tmp_path / "r"asroots (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 thismodule's distance to the repo root, so
Path(__file__).parent(the tests directory, written in 31places under
tests/test_*.py, 33 acrosstests/) does not bind. Loops over a tuple of stringconstants 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 correctlycounts 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
.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
Checks
ruff check .andmypy srcpass locally (65 source files, no issues)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.pyrun 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). Theparallel 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,
mutationpassed in 3h12m33s.Known and deliberately not fixed here: the signed
audit_output_digestin thev5.0.0receipt resolves to no findable artifact and nothing checks it. Trackedseparately. The
v5.0.0receipt itself is valid and binds the tag tree.