fix(anchors): verify_rfc3161 keeps the never-raise rule it prescribes to others - #142
Merged
Conversation
… to others
`register_anchor_type` documents that a verifier "MUST be fail-closed … never
raise for an ordinary bad proof". This first-party implementation did not hold
its own rule: `frozen` and `rp_trust` are consumed with `.get(...)`, so a
non-dict raised a raw `AttributeError` out of a verdict-returning surface.
Recorded by the self-gate run as F3 on 2026-07-31. Re-measured against main
today, sixteen days later, and it still reproduced:
rp_trust=123 -> AttributeError: 'int' object has no attribute 'get'
frozen=123 -> AttributeError: 'int' object has no attribute 'get'
WHY IT SURVIVED SO LONG, and this is the part worth keeping. Two axes of the
never-raise family property were blind to it at once:
MODULE axis -- `anchors_rfc3161` was not in `_MODULES`, so the property
never entered the module at all. Closed separately by the
coverage guard in PR #141.
ARGUMENT axis -- the property fuzzes only the PRIMARY parameter, and both
affected arguments are keyword-only. Even inside the
population the property does not reach them. That is the
finding the self-gate recorded as F2 and it stays OPEN.
So this test file covers this one surface directly instead of pretending the
general sweep does. A fix that relied on the sweep would be closing the instance
while believing it closed the class.
THE FLOOR, NOT A WIDER EXCEPT, for the same reason evalcard and prereg carry
theirs (L1-01): an `except AttributeError` would close this one shape, let the
next type-confusion sibling through, and swallow a genuine internal
AttributeError on top. `BundleFormatError` is in the family's accepted set, so
the surface still DECIDES instead of crashing.
MEASUREMENT PRECONDITION, learned the hard way today: without
`proofbundle[anchors]` the function returns at its optional-import guard BEFORE
these lines, and a probe reads green for a reason unrelated to the defence it
names -- the class the fixture manifest calls
`vacuous_seam_passes_for_a_reason_other_than_the_defence_it_names`. The tests
therefore SKIP honestly when the extra is absent rather than pass vacuously.
BIDIRECTIONAL EVIDENCE. With the floor: 5 green. Without it (temporarily
removed): FAILED failures=1 errors=8, and the failure message reproduces the
original finding verbatim -- "frozen=123 still raises a raw AttributeError:
'int' object has no attribute 'get'". Two of the five tests exist for the other
direction: the documented `rp_trust=None` default and a valid mapping must still
reach a verdict, so the floor cannot have broken the surface it protects.
full suite Ran 2035 tests OK skipped=10 · ruff clean · mypy: no issues in 63
source files.
HONEST SEVERITY, unchanged from the finding: this surface is not exported at
package level and `verify_anchor` wraps every verifier in `except Exception`, so
nothing leaked over the public path. The contradiction was the point -- the
project's own implementation not keeping the rule it prescribes to third-party
authors.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The counter-read rejected the previous commit on my own doubt #2, which I had written down and then not acted on -- the third time today that the same shape came back to me from outside. `isinstance(x, dict)` programs to an implementation. The following logic calls `.get(...)`, so the question is whether the argument is a Mapping, not whether it is exactly a dict. `MappingProxyType`, `OrderedDict` and any dict-like object were being refused for no reason. MEASURED before changing it, so one wrong check would not be swapped for another: every use of `frozen` and `rp` in this function is `.get(...)` -- six call sites, no subscript, no dict-only method, no mutation. `.get` belongs to the `Mapping` protocol, so `collections.abc.Mapping` is not merely more permissive here, it is exactly the right predicate. A floor that rejects valid input is a defect of its own. It is quieter than the crash it replaced, which makes it worse to find, not better. BIDIRECTIONAL EVIDENCE for the correction itself: with `Mapping` 6 tests green; with the old `dict` check restored, FAILED errors=1 -- "BundleFormatError: frozen must be a mapping, got mappingproxy (fail-closed)". The new test therefore discriminates; without that counter-check it would be a test that measures nothing. full suite Ran 2036 tests OK skipped=10 · ruff clean · mypy: no issues in 63 source files. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
b7n0de
pushed a commit
that referenced
this pull request
Aug 16, 2026
…llen gegen meine Zwei Dateien kollidierten, beide weil #141/#142 dieselbe Arbeit schon gelandet hatten, waehrend ich sie lokal noch einmal machte. Aufgeloest zugunsten von main, nicht aus Hoeflichkeit, sondern gemessen: 1. `_ACCEPTED`: ich hatte `OSError` aufgenommen. main nahm nur `FileNotFoundError` — und ihr Kommentar haelt fest, dass der ERSTE Versuch dort ebenfalls `OSError` war und von einer Gegenlesung als REJECT gefangen wurde. `OSError` ist die Basisklasse von `PermissionError`, `TimeoutError`, `BrokenPipeError`; sie alle stillschweigend zu akzeptieren ist ein FAIL-OPEN auf genau der Achse, die diese Eigenschaft verteidigt. Ich habe denselben Fehler ein zweites Mal gemacht, mit derselben Selbstbegruendung. Mains Fassung uebernommen. 2. `emit.load_signer`: mains Typboden ist aequivalent zu meinem und besser dokumentiert. WAS AUS MEINER ARBEIT BLEIBT, weil es main NICHT hat: - `tests/test_load_signer_fd_hazard.py` belegt, dass der Deskriptor NICHT GELESEN wird (er ist nach dem Aufruf noch offen), nicht bloss dass etwas geworfen wurde. Gegen mains load_signer gruen. - Die Unterpaket-Luecke: mains Deckungs-Waechter nutzt `_SRC.glob("*.py")`, also nur die oberste Ebene. Gemessen auf dem zusammengefuehrten Baum: 90 Flaechen in 40 Modulen, und `experimental.enclave` ist NICHT dabei — obwohl es ausgeliefert wird, dokumentierter Importpfad und CLI-Unterbefehl ist. Folgt als eigener Commit. Batterie auf dem zusammengefuehrten Baum: 2161 passed, 9 skipped, 3 xfailed, 228 subtests, ruff clean. Der eine rote Test bleibt der fehlende Vor-Tag-Eintrag.
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.
The contradiction this closes
register_anchor_typetells third-party authors that a verifier "MUST be fail-closed … never raise for an ordinary bad proof". This first-party implementation did not hold its own rule.frozenandrp_trustare consumed with.get(...), so a non-dict raised a rawAttributeErrorout of a surface whose job is to return a verdict.The self-gate run recorded it as F3 on 2026-07-31. Re-measured against
maintoday, sixteen days later:Why it survived sixteen days, which is the more useful half
Two axes of the never-raise family property were blind to it at the same time:
anchors_rfc3161was not in_MODULES; the property never entered the moduleSo this PR adds a test that covers this one surface directly, rather than relying on a sweep that provably does not reach it. Relying on it would be closing the instance while believing the class was closed.
The floor, not a wider except
Same reasoning as the
evalcard/preregfloors (L1-01). Anexcept AttributeErrorwould close this one shape, let the next type-confusion sibling through, and swallow a genuine internalAttributeErroron top.BundleFormatErroris in the family's accepted set, so the surface still decides instead of crashing.A measurement precondition worth stating
Without
proofbundle[anchors]installed,verify_rfc3161returns at its optional-import guard before these lines. A probe run that way reads green for a reason that has nothing to do with the defence it names — the class this repository's own fixture manifest callsvacuous_seam_passes_for_a_reason_other_than_the_defence_it_names. The new tests therefore skip honestly when the extra is absent, instead of passing vacuously. That distinction cost a wrong reading earlier today before it was caught.Bidirectional evidence
FAILED (failures=1, errors=8)frozen=123 still raises a raw AttributeError: 'int' object has no attribute 'get'The failure reproduces the original finding verbatim, so the green is the fix rather than a coincidence. Two of the five tests exist for the other direction: the documented
rp_trust=Nonedefault and a valid mapping must still reach a verdict, so the floor cannot have broken the surface it protects.Full suite
Ran 2035 tests,OK (skipped=10)·ruffclean ·mypy: no issues in 63 source files.Honest severity, unchanged from the finding
This surface is not exported at package level, and
verify_anchorwraps every verifier inexcept Exception, so nothing leaked over the public path. The contradiction was the point — the project's own implementation not keeping the rule it prescribes to others.Not part of the 3.8.0 release candidate (#140), whose delta is a single file. This targets
mainon its own.