Repository navigation
A feature release following 6.3.1, with an experimental SCITT reader, a TRACE verifier and a stricter JSON reader. It adds proofbundle.scitt_ccf, an experimental reader for SCITT Transparent Statements with CCF receipts under the narrow profile scitt-ccf/v1, and proofbundle verify-trace for TRACE v0.2 Trust Records. The strict JSON reader now refuses non-finite numbers, integer literals outside the inclusive range -(2^53 - 1) to 2^53 - 1, and JSON bytes with a UTF-8 byte order mark or encoded as UTF-16 or UTF-32. Review the inputs your code hands a verifier when upgrading.
python -m pip install --upgrade proofbundle==6.4.0
Highlights
- An experimental reader for SCITT Transparent Statements with CCF receipts.
proofbundle.scitt_ccfreads statements under the narrow profilescitt-ccf/v1, which follows draft -06 of the CCF profile, and needs the[scitt]extra. It is reached only by calling it; no verify path of the package reaches it. The Rust cross verifierpb_verify_rs, which is not part of the Python package, gives the same results for statement signatures, Transparent Statements and consistency receipts over shared vectors. proofbundle verify-traceverifies TRACE v0.2 Trust Records. It checks a record against keys, a time window and expectations that the relying party supplies, in the compact JWS form and in the embedded Ed25519 form. A pass is evidence for the relying party's policy, andsafeForAutomationis false in every verdict.- The strict JSON reader rejects non-finite numbers and applies explicit integer and encoding limits.
loads_strictrefusesNaN,Infinity,-Infinityand every number that becomes infinite, integer literals outside the inclusive range -(2^53 - 1) to 2^53 - 1, and JSON bytes with a UTF-8 byte order mark or in UTF-16 or UTF-32, withBundleFormatError. With ansd_jwt_vcwhose issuer signature verifies, a signed JSON payload refused for a duplicate member or one of these limits now demands and failssd-jwt-bundle-binding. A payload that is not JSON at all remains outside the scope of that binding. Review code that hands a verifier such input. - The release run compares its files with the signed candidate. Before any upload, the tag run compares its sdist and wheel with the pair signed in the candidate block of the short soak and of the Rust differential, and stops when the bytes differ. The build tools are selected at fixed versions through
.github/build-constraints.txt; the constraints name versions, not archive hashes.
Verify this release yourself
Use the checksums and build provenance to check the downloaded files. To check the signed pre-tag audit receipt, use a clone at the source commit identified by the build attestation. The commands, prerequisites and limits are in Verifying a published release.
What a receipt still does not prove
A valid signature binds the signed content to the signing key. It does not make a reported result true.
Measured on the reference machine
The budget cost axis was measured on the reference machine on 2026-10-27 using the code at e29a268. The record identifies the run as a reference measurement (ist_referenzmessung=true). All 12 expected axes and all 7 expected combinations passed, and no axis was skipped. The recorded calibration factor maschinenfaktor_schnellstes_ende is 1.0, and the timing-limit setting is latte_aus_der_klammer=schnellstes_ende. These results apply to the listed axes and combinations, not to every reader or input. The measurement record lists every axis and combination.
Details
All 30 entries, one line each, as in the CHANGELOG
Added
proofbundle.scitt_ccfreads a SCITT Transparent Statement with CCF receipts under the narrow profilescitt-ccf/v1of ADR 0009 (PR278, pull request 406). It is experimental and needs the[scitt]extra (cbor2==6.1.4); without it every entry point refuses withno_lib, and nothing falls back to a weaker reader. Its own structural pre-scan,proofbundle._cbor_prescan, decides before cbor2 sees a byte: definite lengths, shortest heads, no floats, unique map keys, tag 18 only at the top and tag 1 only around an integer CWT time claim; cbor2, held inproofbundle._scitt_second_reader, must then read the same values. The three verifiersverify_statement_signature,verify_transparent_statementandverify_consistency_receiptare reachable only by calling them: the anchor type is not registered, and no verify path reaches it. The statement key is selected by the protectedx5chainand never trusted from it, a key is built only by its own type, and consistency receipts have a closed status set of their own. A Receipt meets the CWT Claims rule of a Signed Statement, a text iss and sub in the protected header (RFC 9943 section 6), and a label whose value is null is present, not absent.tools/scitt_ccf_external/holds the measuring tools and their recorded runs: the offline recomputation of third-party receipts, the comparison with microsoft/scitt-verifier, the measurement of section 4 for the working group inSECTION4_WGLC.md, and two differential corpus rounds registered on a local scitt-ccf-ledger.pb_verify_rs verify-scitt-statement-signaturegives the status and verdict ofproofbundle.scitt_ccf.verify_statement_signature(PR290, pull request 408). It exits 0 forconfirmed, 1 forstatement_signature_invalidand 3 for a status that is no verdict about the signature, and a file over its input budget ismalformed, exit 3, as Python reads the same bytes.src/scitt.rsshares no reading code with the Python module: its own CBOR reader, the COSE_Sign1 shape and the statement header types of the CDDL pass, and ES256, ES384, PS256 and PS384 through the RustCrypto cratesp256,p384andrsa, with a signature s >= n refused before thersacrate would reduce it. The crate carries RUSTSEC-2023-0071, a timing leak of a private key; the verifier links only its public-key verification. The 91 vectors oftests/fixtures/scitt_statement_signature/vectors.json, written bytools/scitt_ccf_external/statement_signature_vectors.pyand real statements of the two corpus rounds among them, hold both verifiers to the built verdict intests/test_scitt_statement_signature_parity.pyandcrosscheck.py, and the parity register row is COVERED.pb_verify_rs verify-scitt-transparent-statementgives the result ofproofbundle.scitt_ccf.verify_transparent_statement(PR292, pull request 409). It prints one JSON object with the fields of itsto_dict()but the prose, and exits 0 forconfirmed, 1 for a status that says a check over the evidence failed and 3 for a status that is no verdict about it; the relying party's trust is a JSON file withscitt_statement_keysandscitt_ccf_servicesby issuer.src/scitt_transparent.rsshares no code with the Python module: it runs the CDDL pass of each receipt and of every proof family, computes the roots, holds each receipt to the rules of the profile and verifies its signature over the root. The vectors oftests/fixtures/scitt_transparent_statement/vectors.json, written bytools/scitt_ccf_external/transparent_statement_vectors.py, hold both verifiers to the built verdict, the Transparent Statements a local scitt-ccf-ledger served in the two corpus rounds among them, and the parity register row is COVERED.pb_verify_rs verify-scitt-consistency-receiptgives the result ofproofbundle.scitt_ccf.verify_consistency_receipt(PR297, pull request 410). It prints one JSON object with the fields of itsto_dict()butdetail, and exits 0 forconfirmed, 1 for a status that says a check over the evidence failed and 3 for a status that is no verdict about it.src/scitt_consistency.rsshares no code with the Python module and runs section 4 in the order ofCONSISTENCY_STATUS_ORDER, the receipt signature over the newer root under the relying party's service keys by issuer and kid. The vectors oftests/fixtures/scitt_consistency_receipt/vectors.json, written bytools/scitt_ccf_external/consistency_receipt_vectors.py, hold both verifiers to the built verdict, vectors over three signed states of a local scitt-ccf-ledger among them, where the service's own signature over the newer root stands with a proof computed from the ledger's leaves, and the parity register row is COVERED.- Section 4 consistency receipts get fifteen vectors, an independent checker and a Go reader (PR355, pull request 411). The consistency reader qualifies a success without both tree sizes as
confirmed_without_tree_sizes, andpb_verify_rs verify-scitt-consistency-receiptgives that status, exit 0, witholder_size,newer_sizeandnewer_size_sourcenull, as the Python call without sizes gives them: no size comes from the receipt, and so none from itsccf.v1txid (-06 section 7.3).tools/scitt_ccf_external/section4_vectors/holds the receipt-level vectors S4-01 to S4-15 with their generator, a checker that imports nothing from proofbundle and a manifest; they record two readings of section 4 and leave to the working group which one the draft means. The directory carries one key file,TEST_ONLY_es384_private_key.pem, an ES384 key made for these vectors alone and published on purpose, the key of no service, release or person.tools/scitt_ccf_external/section4_reader_go/is a Go reader of section 4 written from the draft text. - A decision proof for the Rust verifier
pb_verify_rs, and two workflows that run it.formal/z381_pruefentscheidungproves over the domains D1 to D6 thatverify-bundlegives every input exactly one verdict of the closed list, without a panic and the same verdict twice, built from the bytes of the shipped verifier source;paritaet_python.pymeasures the Rust and the Python verdict over the same argv and file bytes; Kani harnesses with decidable bounds carry one planted counter-check per property. The workflowdecision-proofruns the proof with the real binary and five mutants that must turn it red on every pull request, advisory until its first runs are measured. The workflowkani-nightlyruns k5 and k6 every night from a Kani 0.67.0 whose crate and bundle are pinned by sha256. proofbundle verify-traceverifies TRACE v0.2 Trust Records.proofbundle.adapters.trace_record.verify_trace_recordchecks a record against keys, a time window and expectations that the relying party supplies, in the compact JWS form (ES256, ES384, EdDSA) and in the embedded Ed25519 form of section 3.2.2, andcnf.jwkmust name the trusted key that verified. The window values are bounded like every integer of the record. It was written from the specification alone and imports nothing of the TRACE reference;interop/trace/compares it case by case with agentrust-trace 0.11.0. A pass is evidence for the relying party's policy, andsafeForAutomationis false in every verdict.signature.verify_ecdsa_p384is the ES384 primitive it uses, and the ECDSA inventory allows the curve name SECP384R1 for that verifier.docs/TRACE_VERIFIER.mddescribes the verifier and namestool_transcriptas a gap between section 3.1 of the specification and its schema.- Repository tools, outside the installed package. An advisory language check for the title, the body and the commit messages of a pull request,
scripts/pr_language_check.py, runs as the jobpr-languagein.github/workflows/pr-form.yml, also after an edit of the title or the body. It reads the repository's one word list intests/_deutsche_prosa.py, and an event payload without its two commits, or a commit git does not have, is NOT MEASURABLE with exit 2 (tests/test_pr_language_check.py). A line of the body or of a commit message counts as German at two different words of the list, the title at one, and a word written in capitals, such as the acronym DER, counts as none there. It is not a required context of the ruleset protect-main and blocks no merge. - Repository tools, as a follow-up to pull request 377. The language check reads a commit message whole from its object, past a NUL byte and in the encoding its commit declares. It says NOT MEASURABLE with exit 2 instead of clean when no source is named or the range holds no commit, and instead of a traceback with exit 1 when the payload or a message cannot be read. It reads the text with every line boundary as a line feed, then after NFKC and without format characters, and it knows the capital eszett. A quotation in straight double quotes, also over a line break, or in an HTML
<blockquote>, a finding identifier in capitals and a trailer that names a person are no prose, and a quote line and a trailer are read in linear time. Over the 820 commits onmainfrom 2026-09-14 (UTC) to84fc4a3c, four messages are red instead of thirteen, each with German prose. The check runs the copy of the pull request; that is named as the limit of an advisory check (scripts/pr_language_check.py,tests/test_pr_language_check.py).
Changed
- The
scitt-ccf/v1reader verifies a consistency receipt and a statement algorithm as draft -06 of the CCF profile does (PR412, pull request 412). With the sizes the caller gives, a consistency receipt is held to 0 < m < n and to a first sibling on the right; the reader's own anchor rules of the -05 reading, the anchor position for both sizes and the count of left siblings for the older size alone, are gone, with their three statuses and the fieldsanchor_rule_checkedandleft_siblings_checked, since -06 says verification does not establish the anchor (section 4, lines 548-549).verify_statement_signaturegivesoutside_profilefor a statement algorithm other than ES256, ES384, PS256 and PS384, before any key is tried, as the profile check ofverify_transparent_statementdoes (ADR 0009, Decision 6).pb_verify_rsfollows both:verify-scitt-statement-signaturereports such an algorithm asoutside_profile, exit 3, andverify-scitt-consistency-receiptwrites neither anchor field. - The strict JSON reader rejects non-finite numbers and applies explicit integer and encoding limits.
proofbundle._strict_json.loads_strictrefusesNaN,Infinity,-Infinityand every number that becomes infinite, an integer literal outside plus or minus 2^53 - 1 (the integer range the Rust verifierpb_verify_rsreads), and JSON bytes with a UTF-8 byte order mark or in UTF-16 or UTF-32; text with a leading U+FEFF stays theJSONDecodeErrorit was. It raisesBundleFormatError, the error it already raises for a duplicate member, and every verifier that reads through it refuses such an input instead of reading a value RFC 8785 cannot represent. The SD-JWT and KB-JWT checks name the reason of a refusal in their detail instead of reporting every refusal as a duplicate key. - Repository build tooling, outside the installed Python runtime. The distribution-build steps in
.github/workflows/release.yml,.github/workflows/published-artifact-gate.ymland.github/workflows/reusable-build-attest.ymlsetPIP_CONSTRAINTto.github/build-constraints.txt, which fixes build 1.6.1, packaging 26.3, pyproject_hooks 1.3.3, setuptools 84.2.1 and wheel 0.48.0. Their four build jobs fetch the full history for the build epoch. The two steps that produce wheels for attestation or publication also usescripts/build_reproducible.py --expect-backend-fromto compare the wheel's recorded setuptools Generator with the constraint. The constraints select versions; they do not pin dependency archive hashes or prove that a later tag build has the bytes of an earlier candidate build. Measured at 58de280 with Python 3.12.15 and build 1.6.1, setuptools 84.2.1 and 80.9.0 built different wheels from the same commit, with the same sdist. A depth-1 clone at a head that touched onlyaudit_artifacts/selected that head's time and produced a different sdist.tests/test_every_build_path_pins_its_backend.pychecks recognized forms in workflow text and the read-back refusals; its coverage limits are recorded inRESTRISIKO_640.md. - Repository tools, outside the installed package. Before any upload,
.github/workflows/release.ymlcompares the sdist and wheel of the tag run with the pair the owner signed in the candidate block of the short soak and of the Rust differential, and stops when the bytes differ, when a signature does not verify underaudit_artifacts/readiness_trusted_pubkeys.txt, when a source is of another version or kind, or when the two sources name different pairs (scripts/release_dist_matches_signed_candidate.py,tests/test_release_dist_matches_the_signed_candidate.py).scripts/build_reproducible.py --check-wheelreports the digest of the sdist it built, and with--frozen-pairit fails unless that sdist and both wheels are the frozen pair (tests/test_check_wheel_is_bound_to_the_frozen_pair.py). The binding of the signed readiness evidence leaves out the pre-tag receipt as the receipt's own tree digest does, so one evidence commit after the candidate can carry the receipt and the evidence together (scripts/sign_readiness_artifact.py,scripts/audit_candidate_matrix.py,tests/test_the_readiness_binding_excludes_the_receipt_as_the_receipt_does.py).
Fixed
- The
scitt-ccf/v1reader checks the CDDL first, then the status (PR279, pull request 407). One pass validates the statement, its receipts and every vdp proof family completely against the CDDL of the CCF profile and the RFC 9995 parameter types before any status logic runs, and only that pass setsreadable. A label or an array that is present must have its CDDL type, or the artifact ismalformed; absence, and values v1 does not take, stay with the status logic.verify_transparent_statement,verify_consistency_receipt,verify_statement_signatureandrecompute_data_hashall enter through the pass (ADR 0009, Decision 16). Each check is one named rule, and a test takes each away in turn and must fail. The CWT issuer and subject are text on a statement and on each receipt, as RFC 8392 gives them, and another type ismalformed; before, a subject of 7 on a receipt endedoutside_profilewithreadabletrue. Before, a status could come before the shape was checked in full:verify_statement_signatureconfirmed a statement whose unprotected header carried a kid as text, an empty crit, CWT Claims as an array or a content type of -1. - The
scitt-ccf/v1reader follows F1 and F2 of draft -06 of the CCF profile (PR412, pull request 412). An inclusion proof with an empty path is a tree of one leaf, whose root is the leaf hash (-06 section 3), and a consistency proof keeps one path element or more (section 4); the reader refused the empty inclusion path asmalformed, and the valid sample 01-one-leaf-empty-path of the profile repository, signed again under the repository's test key, now confirms. Both tree sizes of a consistency receipt are the caller's: without a newer size from the caller the result isconfirmed_without_tree_sizesat most, whatever the receipt'sccf.v1txid says, and a newer size the caller gives must agree with that txid (consistency_newer_size_mismatch).pb_verify_rs verify-scitt-transparent-statementreads an empty inclusion path as the reader does, so both verifiers givev19-path-emptythe -06 verdict, andv39-path-empty-over-the-leafconfirms a one-leaf receipt in both. pb_verify_rsreads its command line as the Python CLI does under CPython 3.13. It reads argv withargs_os, so a path that is not UTF-8 reaches the file read and a text argument that is not UTF-8 is a usage error (exit 2) instead of a panic (exit 101).verify-bundlerequiresschemaproofbundle/v0.1,signature.alged25519andmerkle.hash_algsha256-rfc6962before the cryptography, refuses--allow-pendingwithout an anchor requirement, takes onlyreceipt,preRegistrationorstatementfor--anchor-target, and reads--expected-tree-sizeas Python'sint()reads it.verify-bundleandverify-relationread their argument values before the signature, an option takes the next word only when argparse takes it as an argument, andverify-bundle,verify-relationandverify-relation-statementread the words after the subcommand with argparse's rules,--pubincluded. These readings follow CPython 3.13 (Unicode 15.1), and 3.12 reads the measured digits the same way. Under Python 3.10 and 3.11 a digit that Unicode added later, such as a Kawi digit in--expected-tree-size, is a usage error for the Python CLI and a number forpb_verify_rs; under Python 3.14 a digit of Unicode 16.0, such as a Garay digit, is a number for the Python CLI and a usage error forpb_verify_rs. A help word and an option that only Python carries out stay different on purpose;CROSS_IMPLEMENTATION_REPORT.mdstates both as a contract, and names the earlier relation form without--pub, whichpb_verify_rsstill reads and the Python CLI refuses, as a residual.- The independent cap1 verifier
cap1_verify_rsreads argv withargs_os, so a file under a name that is not UTF-8 gets a verdict instead of a panic. verify_bundleno longer reads a payload the strict reader refuses as a payload without a claim. With ansd_jwt_vcwhose issuer signature verifies, such a payload skipped thesd-jwt-bundle-bindingcheck, so an eval-claim payload with a duplicate member verified, and with the stricter reader so would one withNaNor an integer outside the reader's accepted range. The binding is now demanded and fails, as the holder-binding check andhf_evalsalready treat the same refusal. A payload that is not JSON at all stays out of scope for that binding. This additional binding check runs only after the issuer signature has been checked and verified; it does not replace the SD-JWT signature checks.site-data.jsonlinks the audit folder of a patch release.scripts/render_site_data.pyderived the audit folder as major100 + minor10, soaudit_linkof 6.3.1 namedaudit_artifacts/630/, the folder of 6.3.0. It now uses the rule of the receipt tools, the version without its dots, and only for a release version X.Y.Z, with or without a suffix that starts with a letter, so 6.4.1rc1 findsaudit_artifacts/641rc1/where its receipt is filed; any other string is a gap with its reason instead of a guess. A minor of two digits is refused as before, and a patch of two digits is refused now as well instead of naming the folder of its minor, each with the release it would collide with. A folder whose name is a non-ASCII digit no longer stops the generator.- Repository tools, outside the installed package. The core-layer gate no longer reads the annotation of a variable in a function body as code. Python never evaluates it (PEP 526), and
x: o.getenv("X") = 1in a function was reported as a read of the environment, atmainand at the head of pull request 395 alike. The value of such an assignment stays judged, and so do the annotations that run: at module level, in a class body and on a function's parameters (scripts/core_layer_gate.py,tests/test_core_layer_gate_reads_no_local_annotation.py). - Repository tools, outside the installed package. The pre-commit hook (
scripts/git-hooks/pre-commit) asks whether every file underscripts/has a decision inMANIFEST.inbefore the commit, when a staged path lies underscripts/, deletions included. It runs the same test node CI runs, against the staged snapshot written out withgit checkout-indexand not against the working tree (tests/test_the_hook_asks_before_the_long_job.py). Without pytest it says so on stderr and lets the commit through; CI stays the gate. - Repository tools, outside the installed package. The mutation gate reads the abort banner of pytest only outside captured test output, and a captured block closes only at a separator of the width of the line that opened it, so a failing test that prints a line such as
! ordinary diagnostic !no longer turns a finished run into NOT MEASURABLE (scripts/mutation_check.py). The text reader of the test-manifest gate reads the names of each import statement from the token stream, so it agrees with the syntax tree onimport os, unittest, a trailing comment, an alias and a backslash continuation (scripts/test_manifest_gate.py,tests/test_codex_199_banner_and_import_path.py). - Repository tools, outside the installed package. The release-scope title gate reads the title of a follow-up,
[<version> <ID>-FOLGE] type(scope): subject. On a branch of its own, which no row of the scope names, such a title is judged against the In row of its parent and needs no row of its own; before, the form did not read the suffix, and the gate was green only because the follow-up's branch stood outside the scope. A parent that is no In row with a branch leaves its follow-up outside the scope, where the parent stands, and the verdict names the parent; the branch of a row carrying the suffix is red, because the landing card counts the line only by its plain identifier and never counts a follow-up. A second bracket with the suffix counts as an identifier like any other, in a follow-up title and in the title of a row's own branch, and a branch or a title that is no text is red with its reason instead of ending the gate in an exception. Without--version, in a clone that shows no release tag, a follow-up chooses the release by the row of its parent, as the parent's own branch would; before, its branch, which no scope file names, chose the newest candidate, and a follow-up of a line of the older candidate was outside the scope with exit 0 (scripts/b7_release_scope_title_gate.py,tests/test_release_scope_title_gate.py,tests/test_release_scope_landing_card.py,tests/test_release_scope_title_gate_folge_waehlt_die_version.py). - Every tool that reads a path list from git reads it as git names the paths (
scripts/check_version_and_changelog.py,scripts/audit_output_aufloesbar.py,scripts/verify_pre_tag_receipt.py,scripts/neue_zeilen_sind_englisch.py). The mutant guard's fix below was one instance of a class: without-z, git quotes a path that holds a byte outside ASCII, and the quoted string opens no file. Measured in throwaway repositories, each against the state before: the version gate's sweep for undeclared release claims skippeddocs/prüfung.md; the digest-resolvability check saidNICHT_AUFLOESBARfor a tracked record under such a name and did not count it; the third-party receipt verifier did not see a receipt under such a name; the language gate reported a German line indocs/prüfung.mdunder the file before it, judged green when no file came before it, and said NOT MEASURABLE for such an untracked file. The lists are read with-zand as bytes now (one name that is not UTF-8 would otherwise empty the version gate's list through its text decoder), and the language gate decodes a diff header with the mutant guard's decoder. A sweep overscripts/andtools/holds every git call that lists paths to-zor to a named reason; five calls keep the quoted form, among them the two tree digests, whose listing is hashed and never opened. No tracked path at this change needs quoting, so no verdict on this repository changes. A name that is not UTF-8 decodes to a lone surrogate, and each of the four tools raised on one while writing its report, with exit 1, the exit code of a finding (measured in every output form each tool has). The reports write such a name with backslash escapes now, and the JSON forms write every character outside ASCII as its JSON escape, so the name reads back as it was under any output encoding. When git does not list the tracked files, the version gate's sweep for undeclared current-version claims no longer reads that as a tree without files and prints OK over it: in a tree without git its summary names the sweep as not measured, and in a checkout git cannot list it fails. - The mutant guard reads a path git quotes (
scripts/mutant_signature_guard.py). git writes a path with a byte outside ASCII, a double quote, a backslash or a control character in a diff header in double quotes with C escapes ("b/src/proofbundle/pr\303\274fung.py"). The guard read that text as the path, it matched no security path, and a stagedif False:in such a file was reported clean with exit 0 (measured in a throwaway repository; plain names and names with a space were caught). The header path is decoded now, the self-test plants a mutant under a non-ASCII name, and the tests cover a non-ASCII name, a double quote and a backslash in both modes. No file undersrc/proofbundlecarries such a name today. A newline in a name is decoded too, and the scope pattern now reads it with DOTALL between\Aand\Z: before, its.stopped at the decoded newline, and a staged or committedif False:insrc/proofbundle/a\nb.pywas reported clean with exit 0. The tests plant that name in both modes, and against the guard without this change the three new cases are red. The guard names the header prefixes of the diff it reads, sodiff.mnemonicPrefixin a developer's configuration no longer turnsb/intoi/and leaves the staged form reporting clean over a stagedif False:; the language gate's working-tree form had the same gap withw/. - Within parsed Python code and the operations it recognizes, the core-layer gate checks uses against the bindings its analysis identifies. It treats
TYPE_CHECKINGas a trusted guard only within that analysis and when the analysis finds it unchanged (K6,tests/test_core_layer_gate_judges_every_binding.py). It recognizes additional forms of the operations it checks (tests/test_core_layer_gate_sees_every_spelling.py), including writes throughgetattr(module, "__dict__"),object.__getattribute__,module.__setattr__,object.__setattr__, aliases ofsetattrorvars, a function's__globals__, a frame'sf_globals,sys.modules, and dotted names such astyping.TYPE_CHECKING. Names introduced by star imports, re-exports and match captures whose value the analysis can read are resolved in their binding context. So are a name bound undernonlocal, a name an assignment expression binds inside a comprehension, which Python binds in the scope around the comprehension, and the annotations of a function, andgetattr(x, "name")andx.__getattribute__("name")with a literal name stand forx.nameinside a chain. The value of a binding underglobalornonlocalis read where it stands, so a lazy import (global _os, thenimport osand_os = osin a function) is resolved, and a chain of such reads is resolved once per link.getattr(x, "name", default)stands forx.nameor the default, and a default the analysis cannot resolve stands beside the names it can as a possible unresolved result; a reader imported through another module of the package stays a reader, in the findings and in the import closure. Reading a namespace, as inprint(vars(x))orx.__dict__ is None, does not by itself invalidate every guard. An object that the analysis cannot rule out as a module is treated as a possible module, a parameter among them whatever else the function assigns to it. An object built by a class of the same module counts as no module only when the class is plain: bound once, with no decorator, no metaclass or other keyword, no__new__bound in its body in any form that binds a name, a match capture among them, and no attribute__new__set in its module, also by a setattr whose name is built from constants or whose arguments the gate cannot assign, and onlyobjector such classes of the module as bases; a name computed from anything else stays out of reach. An exception for the identified write insrc/proofbundle/canonical.pyis recorded by name inscripts/core_layer_modules.json, with its reason, its count and the source of its line. That permission does not by itself establish the runtime effects of the write or thatTYPE_CHECKINGis unchanged. The analysis does not follow module attributes selected by computed names, as ingetattr(module, name), treats the first parameter of a method as no module, so a method called through its class with a module as that argument is not seen, and does not cover code it does not parse. A passing result is limited to these checks and is not a proof that no other runtime binding or write exists. - Repository tools, as a follow-up to pull request 375. The mutant guard reads a header only outside a hunk and splits the diff at its line feeds only, so an added line
++ 1no longer moves the lines after it out of the scope, and a form feed no longer cuts an addedif False:off its+. It asks git for the quoted header form whatevercore.quotePathsays, keeps an octal escape above\377as it stands instead of raising, and shows a name with a character that is not printable in git's quoted form, so a newline in a name no longer begins a line of its own in the log (scripts/mutant_signature_guard.py). The language gate reads the hunks of its diff the same way (scripts/neue_zeilen_sind_englisch.py). The JSON forms of three tools write every character outside ASCII as its JSON escape (scripts/audit_output_aufloesbar.py,scripts/neue_zeilen_sind_englisch.py,scripts/verify_pre_tag_receipt.py). The readiness tree digest pinscore.quotePath=trueas the receipt digest does (scripts/sign_readiness_artifact.py), the candidate matrix reads the paths changed after a bound commit with-zand trims none (scripts/audit_candidate_matrix.py), and the mutation gate and the release renderer decode a name that is not UTF-8 as Python decodes a file name (scripts/mutation_check.py,scripts/render_release.py). - Repository tools, as a follow-up to pull request 415. The mutant guard and the language gate ask git for their diff in a fixed form:
diff.interHunkContext=0anddiff.suppressBlankEmpty=falseon the command line, and--no-color,--no-ext-diff,--no-textconvand--text. With the two settings in a session's configuration, an empty context line between two changes lost the hunk count, and the guard reported clean over a staged mutant, the language gate green over a German line. An external diff program, the text conversion of a diff driver or a-diffattribute, which a tracked.gitattributescan set, made git print no hunk, and both reported clean over a diff they had not read, andcolor.ui=alwaysdid the same to the language gate, which asked without--no-color. A line an open hunk cannot hold now makes the guard exit 2 and the language gate report NOT MEASURABLE, instead of being passed over (scripts/mutant_signature_guard.py,scripts/neue_zeilen_sind_englisch.py). The guard's self-test plants a-diffattribute, and its error message names the git subcommand again. Both read what git writes as bytes, so a binary file under--textand a lone CR no longer end them in a traceback or move a line out of its hunk, and the guard checks every piece of an added line that Python reads as a statement of its own. Every git call of both runs in a built environment that passes the names git needs to find the repository and its index and pins the rest, soGIT_LITERAL_PATHSPECSand its two neighbours no longer make the language gate green andGIT_TRACE=/dev/stdoutno longer makes the guard skip its range; replacement refs are switched off. A failed git call and a file on a security path that is not UTF-8 make the guard exit 2. Both answer in git's line space, at line feeds: a lone CR, a form feed, U+2028 and the other characterssplitlines()ends a line at no longer move areturn Trueout of the change, a German line after a fence onto the fence, or an allow marker off its line. Markdown is read in CommonMark's lines, so a closing fence or a quotation marker after a lone CR still counts, and of a git line that holds code and prose only the prose is read. A byte order mark no longer hides a mutant, a NUL and a damaged base object make the guard exit 2, a file system monitor no longer hides a change from the working-tree form, and the text report of the language gate escapes a CR or LF in a name or a line. - Repository tools, outside the installed package, as a follow-up to pull request 371. A captured block of the mutation gate closes only at a separator with a title, so a line of the run's width that a test prints without one no longer puts the next line back on the top level; the text fallback reads
InterruptedandINTERNALERRORoutside captured output as well; and the head of a captured block is read in linear time (scripts/mutation_check.py). The token reader of the test-manifest gate reads a module name after NFKC, as the syntax tree does, and counts a source with a NUL byte as importing instead of stopping withSystemErrorunder CPython 3.12. Both readers leave out a test source that does not decode as UTF-8, and the syntax tree also one its parser refuses withValueError(a NUL byte under 3.10),RecursionError(a long chain from 3.11 on) orMemoryError(a source that overflows the parser's stack, on every version); each of these stopped the gate with a traceback (scripts/test_manifest_gate.py,tests/test_codex_199_banner_and_import_path.py). - Repository tools, as a follow-up to pull request 395. The core-layer gate reads a dotted name for a patch helper also when it is built from constants, by a name, a concatenation, an f-string, a
joinor a name bound in another module of the package. An operator over a namespace reads it only when the other operand runs no code of the module, soW() == vars(typing)with a class of the module that defines__eq__is a possible change.Path(...).cwd()andPath(...).home()are environment reads. A module the gate cannot read or parse, also outside the core, and a module too deep for the parser are not measurable (exit 2) instead of passed over or a traceback. A chain of aliases is followed to its end, and the reviewed change of the guard inscripts/core_layer_modules.jsonis bound to the source of its line as well, with the line numbers of its reason corrected. The bindings the analysis does not follow and the finite lists of operations are named in the gate. A literal attribute read is read in each spelling the gate reads an attribute call in: through__call__, asC.__getattribute__(x, "name")for any classC, and through a__getattribute__bound to a name or imported from a module (F-B). A conditional, a boolean and an assignment expression stand for each value they may give, as the value of a binding and as a receiver (F-C). A receiver read through a call or through such an expression keeps counting as unresolved for the file and environment methods, sogetattr(sys, "stdin").read()andgetattr(zipfile, "ZipFile").openare file findings, whichmaindoes not report, and underfrom __future__ import annotationsthe annotations of a function are read in the module scope, wheretyping.get_type_hintsevaluates them (F-D), and the module is read a second time with them in the scope of the function around the def, always, asmainreads it, since a caller may hand an evaluator those names in more ways than the gate can tell apart; a finding of either reading counts, once. A name stands for at most 4096 qualified names, so a value that grows around a cycle of attributes no longer runs the gate out of memory, and a name bound only after that limit is not seen, and a reading that does is not measurable (exit 2) with its report. A diamond of plain classes builds no possible module: each class is judged once and its verdict kept, so a shared base reached a second time is not taken for a cycle (scripts/core_layer_gate.py,tests/test_core_layer_gate_sees_every_spelling.py). As a follow-up to pull request 420, the string a name is read as is made once per scope, name and depth, so a counter assigned eight times and then used as an index no longer takes a minute, a class counts as plain only when no__new__is bound in its body and no attribute__new__is set in its module, the reviewed change of the guard reads its lines asastcounts them, and a modules file nested too deep is not measurable (tests/test_core_layer_gate_counter_reading_420.py).
Full changelog · Known limitations for 6.4.0 · Earlier risk record · Release scope · proofbundle.dev
Assisted by little helpers.