feat(validation): report an unexpected exception as a validator fault - #151
Merged
Conversation
A check raising something it does not expect had two possible fates and both were wrong: reported as FAIL it named the user's document for a defect of ours, and left to propagate it discarded the verdicts already computed for every other target in the run. - new `fault` status, fatal to the verdict like `fail` but distinct from it: no verdict was produced, so the document is neither condemned nor cleared - the fault keeps the id of the check that raised it, so no new check id enters the public interface - guards per check section, plus a last-resort guard per file, so one broken check costs one check - faults lead `failure_reasons` and are counted separately in the CLI Closes #145
Contributor
Release previewMerging this PR would release v0.20.0 (current: Changelog preview (truncated)## v0.20.0 (2026-09-11)
### Features
- **validation**: Report an unexpected exception as a validator fault
([`87a3852`](https://github.com/OO-LD/oold-python/commit/87a3852e371b07ca3e639b80a34b0201bbc447ba))
### Testing
- **validation**: Cover genuine remote retrieval and the warm cache
([#139](https://github.com/OO-LD/oold-python/pull/139),
[`7fd050f`](https://github.com/OO-LD/oold-python/commit/7fd050f513dafe715d5fe6dfae6bdf0e1db788a8))
- **validation**: Cover the fault paths the guard-shaped tests missed
([`a243160`](https://github.com/OO-LD/oold-python/commit/a24316079d4f40b0ff8c7dd244d66b6b1bb02e80))
Preview via python-semantic-release and conventional commits. |
Contributor
📊 Benchmark ResultsClick to see benchmark comparisonThreshold: 1.3x (30% slower triggers a regression warning) Note: Benchmarks are informational only and won't fail the build. 💡 Tip: Download the |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
codecov reported the patch as covered; measuring the lines showed the two bespoke paths were not. Both sections that return early instead of using _guard had no test, and neither did the CLI rendering. Also drops Check.line(), which had no callers and duplicated the CLI's own rendering closely enough that widening the status column had to be done in both.
Contributor
📊 Benchmark ResultsClick to see benchmark comparisonThreshold: 1.3x (30% slower triggers a regression warning) Note: Benchmarks are informational only and won't fail the build. 💡 Tip: Download the |
simontaurus
added a commit
that referenced
this pull request
Sep 11, 2026
#151 added a status that makes the exit code non-zero and left the how-to saying exit is 0 "only when no check failed", which a fault also breaks.
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.
Closes #145. Unblocks the remaining eleven sites in #127.
The shape chosen
The fault carries the id of the check that raised it, rather than reporting under a new id of its own. So no new check id enters the public interface - the interface change is the new status, which the MCP
statusfield now documents.That also makes the report say which part of the validator broke using the same identifier that names what it was trying to establish:
roundtrip.generatedfaulting means frame derivation or the RDF round-trip broke, and nothing has to map a genericvalidator.faultback to a subject.What it looks like
With
roundtripforced to raise, overoold-schema/examples:Exit 1.
0 failedis the point: nothing was concluded about the documents. All 21 targets still reported - the run did not abort.Changes
report.py:FAULTstatus,Check.is_fault,Report.faults(),countsextended.Report.passedrequires nofailand nofault.failure_reasonsputs faults first, because a finding tells the reader to change their document while a fault tells them part of the answer is missing.pipeline.py: a_guardcontext manager binding a check id lexically to the region it guards, applied per check section. Two places take an early return instead, where the section produces state everything downstream needs (schema.refs,generate.satisfiable) - carrying on would report one defect once per section that tripped over it. Plus a last-resort guard per file invalidate_directory.cli.py:FAULTstyled magenta, louder than red, because the reader should not go looking for it in their own document. The count appears only when non-zero - a permanent0 fault(s)would train people to skip the field.Note on #127
context.remote's catch is still broad, so nothing reaches its guard yet; its comment said the narrowing "waits on #145" and now says what actually remains. Narrowingpredicates.pytoJsonLdErrorand retargetingtest_a_processor_failure_is_not_downgraded_to_a_coverage_warning, as #145 describes, is #127's work and not done here.Verification
pytest: 587 passed, 9 skipped, including two new tests - one that a raising check faults under its own id and produces no finding, one that a fault fails the run without discarding the other verdictspre-commit: cleanoold-schema/examplesunchanged at 380 ok / 0 failedBoth checks that call
roundtripfault when it raises, each under its own id. That is the behaviour, not a leak: both genuinely broke.