fix(ci-formats): a check that could not complete is not a check that passed - #105
fix(ci-formats): a check that could not complete is not a check that passed#105ChelseaKR wants to merge 1 commit into
Conversation
…passed
The engine publishes one finding for a rule whose check throws -- "This check
could not complete. No package conclusion was made." -- and the run exits 2.
Console, HTML and checklist print that sentence. SARIF and JUnit count elements
instead of reading them, and both counted this one as a rule that ran and was
content:
<testsuites name="ceqa-preflight" tests="20" failures="0" errors="0" skipped="6">
<testcase name="PDF-006: Active PDF content: internal rule error" ...>
<system-out>WARNING PDF-006: This check could not complete. ...</system-out>
</testcase>
A testcase with no failure, error or skipped child is a passing test case, and
`errors="0"` was a literal no input could move -- on the run the tool itself
exits 2 for. SARIF reported `executionSuccessful: true` the same way.
`<skipped>` was already reserved for `not_run`, for the reason SkippedCheck
exists: a report of what ran must not read as a report about the package. A
check that started and threw is the same gap arriving by the other door, and
JUnit already separates "the assertion did not hold" from "the test could not
run". `<error>` is now reserved for it, `errors` is counted, and SARIF's
`executionSuccessful` is read off the report with an error-level
toolExecutionNotification naming the rule so the false is diagnosable.
Nothing moves out of sight: the warning stays in SARIF `results` and in the
JUnit case list. The prose formats are untouched.
`Finding` gains `check_completed` (default true) because the fact otherwise
existed only inside a localized sentence, and neither CI format reads prose.
`report_schema_version` moves to 1.2 -- additive, as 1.1 was for `not_run`.
No new localizable strings: `make i18n` still reports 210 at parity.
0c90270 to
a0de9a6
Compare
|
Rebased onto Two conflicts, both mechanical, both in prose rather than code:
Nothing about this branch's substance moved. The One sequencing note that appeared today. #90 (agency-local rule packs) also proposes Prepared with AI assistance; reviewed before submission. |
Draft: this bumps
report_schema_versionto1.2, which is a published contract. The last section names the two calls that are yours.What was wrong
RuleEngine.runcatches a check that throws, publishes one warning finding in place of everything the rule did not conclude — "This check could not complete. No package conclusion was made." — and sets the run's exit code to2.Console, HTML and the checklist print that sentence, so a person reads it. The two CI formats added in #104 count elements rather than reading them, and both counted this one as a rule that ran and was content.
Measured on
main(6d948f7), driving the realcheck_packagepath over a real synthetic NOE package with one registry entry raising:A
<testcase>whose only child is<system-out>is a passing test case. A JUnit job summary totals that run as 20 tests, 0 failures, 0 errors, 6 skipped, 14 passed — with the check that crashed among the fourteen — on a run the tool itself exits2for. Anderrors="0"was a hard-coded literal: the one attribute JUnit provides for exactly this state was the one value no input could ever move.SARIF said
executionSuccessful: truethe same way, on the same run.Why this is the same defect twice, not a new feature
SkippedCheckexists so "a report that lists only what ran cannot be read as a statement about the whole package", and #104 carried that into both new formats: SARIFtoolExecutionNotifications, JUnit<skipped>, with an explicit test that a skip is never smuggled in as a passing result.A check that started and threw is the same gap arriving by the other door. It reaches the report having evaluated nothing, which is precisely what #61 established when it stopped the engine publishing a crashed rule's partial outcomes ("a check that did not finish did not evaluate anything"). The disclosure was propagated to
not_runin both formats and not to this.What changed
<error>is now reserved for a check that could not complete, symmetrically with<skipped>for one that never started. JUnit already separates "the assertion did not hold" from "the test could not run"; this is the second.errorsis counted rather than asserted.executionSuccessfulis read off the report, and each such rule gets an error-leveltoolExecutionNotificationwithassociatedRule, so thefalseis diagnosable rather than bare.resultsand in the JUnit case list. Both formats gain, neither substitutes.Findinggainscheck_completed(defaulttrue). The fact otherwise existed only inside a localized English sentence, and neither CI format reads prose; sniffing the title or message would have been both fragile and locale-dependent. Both CI formats stay unlocalized, so no new localizable strings:make i18nstill reports 210 messages, parity across en, es, the same 210 as before this branch.How it was verified
make lock-check lint typecheck security audit i18nall clean (auditdid reach the network here and passed). Full suite 554 passed, branch coverage 95.40% against the 90% floor. README's gate count 544 → 554, whichtest_readme_test_total_matches_what_pytest_collectsenforces.Three negative controls. Each sabotage was read back from disk to confirm it landed before running, then restored from a
cp -pbyte copy verified byshasum:errorsback to the literal"0"assert suite.get("errors") == "1"(assert '0' == '1')executionSuccessfulback toTrueassert ...["executionSuccessful"] is False(assert True is False)check_completed=Falsedeletedtest_the_internal_error_finding_records_that_the_check_did_not_complete(assert True is False)shasummatches0b88e864…/ab70ca2a…)The third control is the one worth reading. It fails only the engine test and leaves all eight renderer tests green, because those build findings directly rather than running a rule. That is exactly the absorption this pair of tests exists to prevent: without the engine-level assertion, the renderers could keep their contract while the one producer that sets the flag quietly stopped setting it. The paired positive assertions (
executionSuccessful is Trueon a completed run,errors == "0"on a completed run,check_completed is Trueon an ordinary finding) are there so none of the three can be satisfied by a constant.What needs your call
The schema bump.
report_schema_versiongoes1.1→1.2. Additive, exactly as1.1was fornot_runin Name the checks that did not run, in every report format #42: a 1.1 reader that ignorescheck_completedstill reads every report correctly, anddiffing.SUPPORTED_REPORT_SCHEMA_MAJORis"1", sodiffcompares 1.1 and 1.2 reports without complaint. If you would rather not move the minor for one boolean, say so and I will drop it. Two test assertions carry the literal (tests/test_manifest.py,tests/test_i18n.py).A related gap I did not fix. On that same run,
_scope_linestill emits "Every check that applies to this filing type ran." whenevernot_runis empty — including when a check ran and threw. Defensible as written (the check did start), and the console prints the "could not complete" warning three lines below it, so no console reader is misled. I left it because correcting the wording means new localizable strings, which would putmake i18nred until someone writes the Spanish, and theescatalog is already a maintainer draft pending Qualified CEQA and Spanish review of the AI prompts, refusal suite, and corpus #49. Worth a look separately if you want the scope line to mean "ran to completion".Not touched, so a later pass can pick them up: the
diffverb's summary line counts only finding deltas and states no skip counts (the console and HTML both list moved skips in a section below it, so nothing is hidden — only uncounted); andai … --outputtakes a bare file path, so pointing it at a directory raises an uncaughtIsADirectoryErrorrather than behaving likecheck --output/diff --output(#101).Prepared with AI assistance; reviewed before submission.